Compare commits

3 Commits
Author SHA1 Message Date
Atakan Doğan ÖzbanandCursor fc45c35dbe 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>
2026-07-29 20:25:55 +02:00
Atakan Doğan Özban 202dc16a52 Add project files. 2026-02-13 23:40:52 +01:00
Atakan Doğan Özban cf37f33a1e Add .gitattributes and .gitignore. 2026-02-13 23:40:49 +01:00
52 changed files with 61 additions and 304 deletions
-15
View File
@@ -361,18 +361,3 @@ MigrationBackup/
# Fody - auto-generated XML schema # Fody - auto-generated XML schema
FodyWeavers.xsd FodyWeavers.xsd
# User-uploaded content (keep folder structure via .gitkeep, ignore actual files)
Content/uploads/affiliate-logos/*
!Content/uploads/affiliate-logos/.gitkeep
Content/uploads/projects/*
!Content/uploads/projects/.gitkeep
Content/uploads/projects-media/*
!Content/uploads/projects-media/.gitkeep
Content/uploads/wallpaper/*
!Content/uploads/wallpaper/.gitkeep
App_Data/Uploads/*
!App_Data/Uploads/.gitkeep
# Old backup folder, not needed
bin-bckp/
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

+2 -2
View File
@@ -115,7 +115,7 @@ namespace atakanozbancom.App_GlobalResources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Affiliate Links. /// Looks up a localized string similar to Support my work 💙.
/// </summary> /// </summary>
public static string AffiliateLinksTitle { public static string AffiliateLinksTitle {
get { get {
@@ -250,7 +250,7 @@ namespace atakanozbancom.App_GlobalResources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Affiliate Links. /// Looks up a localized string similar to Support my work 💙.
/// </summary> /// </summary>
public static string NavAffiliateLinks { public static string NavAffiliateLinks {
get { get {
+2 -2
View File
@@ -151,10 +151,10 @@
<value>My Projects</value> <value>My Projects</value>
</data> </data>
<data name="NavAffiliateLinks" xml:space="preserve"> <data name="NavAffiliateLinks" xml:space="preserve">
<value>Affiliate Links</value> <value>Support my work 💙</value>
</data> </data>
<data name="AffiliateLinksTitle" xml:space="preserve"> <data name="AffiliateLinksTitle" xml:space="preserve">
<value>Affiliate Links</value> <value>Support my work 💙</value>
</data> </data>
<data name="AffiliateLinksIntro" xml:space="preserve"> <data name="AffiliateLinksIntro" xml:space="preserve">
<value>If you'd like to support my work, you can use the links below. It costs money for me to run and maintain these services, and using them may earn me a small benefit at no extra cost to you.</value> <value>If you'd like to support my work, you can use the links below. It costs money for me to run and maintain these services, and using them may earn me a small benefit at no extra cost to you.</value>
+2 -2
View File
@@ -151,10 +151,10 @@
<value>Projelerim</value> <value>Projelerim</value>
</data> </data>
<data name="NavAffiliateLinks" xml:space="preserve"> <data name="NavAffiliateLinks" xml:space="preserve">
<value>Referans Linklerim</value> <value>Çalışmamı Destekleyin</value>
</data> </data>
<data name="AffiliateLinksTitle" xml:space="preserve"> <data name="AffiliateLinksTitle" xml:space="preserve">
<value>Affiliate Linklerim</value> <value>Çalışmamı Destekleyin</value>
</data> </data>
<data name="AffiliateLinksIntro" xml:space="preserve"> <data name="AffiliateLinksIntro" xml:space="preserve">
<value>Çalışmalarımı desteklemek isterseniz aşağıdaki linkleri kullanabilirsiniz. Bu servisleri çalıştırmak ve sürdürmek benim için maliyetli, bu linkleri kullanmanız size ekstra bir maliyet çıkarmadan bana küçük bir fayda sağlayabilir.</value> <value>Çalışmalarımı desteklemek isterseniz aşağıdaki linkleri kullanabilirsiniz. Bu servisleri çalıştırmak ve sürdürmek benim için maliyetli, bu linkleri kullanmanız size ekstra bir maliyet çıkarmadan bana küçük bir fayda sağlayabilir.</value>
+8
View File
@@ -13,6 +13,14 @@ namespace atakanozbancom
{ {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Keep old /affiliate URLs working
routes.MapRoute(
name: "AffiliateRedirect",
url: "affiliate/{action}/{id}",
defaults: new { controller = "support", action = "Index", id = UrlParameter.Optional },
new[] { "atakanozbancom.Controllers" }
);
routes.MapRoute( routes.MapRoute(
name: "Default", name: "Default",
url: "{controller}/{action}/{id}", url: "{controller}/{action}/{id}",
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 MiB

+2
View File
@@ -62,6 +62,7 @@ namespace atakanozbancom.Controllers
iframe = p.iframe iframe = p.iframe
}; };
// NEW MEDIA TABLE
if (mediaLookup.TryGetValue(p.id, out var list)) if (mediaLookup.TryGetValue(p.id, out var list))
{ {
vm.medias = list.Select(m => new ProjectMediaVM vm.medias = list.Select(m => new ProjectMediaVM
@@ -76,6 +77,7 @@ namespace atakanozbancom.Controllers
}).ToList(); }).ToList();
} }
// fallback legacy
if (vm.medias == null || vm.medias.Count == 0) if (vm.medias == null || vm.medias.Count == 0)
{ {
if (!string.IsNullOrWhiteSpace(p.iframe)) if (!string.IsNullOrWhiteSpace(p.iframe))
@@ -10,10 +10,11 @@ using atakanozbancom.Models.classes;
namespace atakanozbancom.Controllers namespace atakanozbancom.Controllers
{ {
public class AffiliateController : Controller public class SupportController : Controller
{ {
private readonly Context db = new Context(); private readonly Context db = new Context();
// GET: /Support
[HttpGet] [HttpGet]
public ActionResult Index() public ActionResult Index()
{ {
@@ -56,6 +57,7 @@ namespace atakanozbancom.Controllers
return View(model); return View(model);
} }
// POST: /Support/ChangeLanguage
[HttpPost] [HttpPost]
public ActionResult ChangeLanguage(string lang) public ActionResult ChangeLanguage(string lang)
{ {
+12
View File
@@ -6,6 +6,8 @@ using System.Web.Helpers;
using System.Web.Mvc; using System.Web.Mvc;
using System.Web.Optimization; using System.Web.Optimization;
using System.Web.Routing; using System.Web.Routing;
using System.Diagnostics;
using System.Web.Configuration;
namespace atakanozbancom namespace atakanozbancom
{ {
@@ -13,10 +15,20 @@ namespace atakanozbancom
{ {
protected void Application_Start() protected void Application_Start()
{ {
var mk = WebConfigurationManager.GetSection("system.web/machineKey")
as MachineKeySection;
Debug.WriteLine(
$"[MK] validation={mk.Validation}, decryption={mk.Decryption}, " +
$"validationKeyLen={mk.ValidationKey?.Length}, decryptionKeyLen={mk.DecryptionKey?.Length}"
);
// your existing stuff...
AreaRegistration.RegisterAllAreas(); AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes); RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles); BundleConfig.RegisterBundles(BundleTable.Bundles);
} }
protected void Application_BeginRequest() protected void Application_BeginRequest()
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2026 Atakan Doğan Özban
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+2
View File
@@ -32,8 +32,10 @@ namespace atakanozbancom.Models.classes
[Display(Name = "Sort Order")] [Display(Name = "Sort Order")]
public int sort_order { get; set; } public int sort_order { get; set; }
// Path of the already-uploaded logo (used when editing)
public string existingImage { get; set; } public string existingImage { get; set; }
// New logo file (required on create, optional on edit to replace it)
public HttpPostedFileBase logoFile { get; set; } public HttpPostedFileBase logoFile { get; set; }
} }
} }
+1
View File
@@ -10,6 +10,7 @@ namespace atakanozbancom.Models.classes
public string DescTR { get; set; } public string DescTR { get; set; }
public string DescEN { get; set; } public string DescEN { get; set; }
// NEW:
public List<projectmedia> medias { get; set; } = new List<projectmedia>(); public List<projectmedia> medias { get; set; } = new List<projectmedia>();
} }
} }
+14
View File
@@ -3,8 +3,17 @@ using System.Web;
namespace atakanozbancom.Models.classes 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 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( private static readonly Regex LinkPattern = new Regex(
@"\[([^\]\r\n]{1,200})\]\((https?://[^\s)]{1,2000})\)", @"\[([^\]\r\n]{1,200})\]\((https?://[^\s)]{1,2000})\)",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
@@ -14,8 +23,12 @@ namespace atakanozbancom.Models.classes
if (string.IsNullOrWhiteSpace(raw)) if (string.IsNullOrWhiteSpace(raw))
return string.Empty; return string.Empty;
// 1) Neutralize everything (script/tags/attributes) up front.
var encoded = HttpUtility.HtmlEncode(raw); 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 withLinks = LinkPattern.Replace(encoded, m =>
{ {
var label = m.Groups[1].Value; var label = m.Groups[1].Value;
@@ -23,6 +36,7 @@ namespace atakanozbancom.Models.classes
return $"<a href=\"{url}\" class=\"text-secondary text-decoration-none\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">{label}</a>"; 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 var withBreaks = withLinks
.Replace("\r\n", "\n") .Replace("\r\n", "\n")
.Replace("\n", "<br />"); .Replace("\n", "<br />");
+1
View File
@@ -14,6 +14,7 @@ namespace atakanozbancom.Models.classes
public string image { get; set; } public string image { get; set; }
public string iframe { get; set; } public string iframe { get; set; }
// NEW
public List<ProjectMediaVM> medias { get; set; } = new List<ProjectMediaVM>(); public List<ProjectMediaVM> medias { get; set; } = new List<ProjectMediaVM>();
} }
+1
View File
@@ -9,6 +9,7 @@ namespace atakanozbancom.Models.classes
[Key] [Key]
public int id { get; set; } public int id { get; set; }
// Public URL of the current wallpaper. Null/empty falls back to the bundled default.
[StringLength(500)] [StringLength(500)]
[Column("wallpaper")] [Column("wallpaper")]
public string wallpaper { get; set; } public string wallpaper { get; set; }
+1
View File
@@ -9,6 +9,7 @@ namespace atakanozbancom.Models.classes
[Key] [Key]
public int id { get; set; } public int id { get; set; }
// Font Awesome class(es), e.g. "fa-brands fa-youtube"
[Required, StringLength(100)] [Required, StringLength(100)]
[Column("icon")] [Column("icon")]
public string icon { get; set; } public string icon { get; set; }
-137
View File
@@ -1,137 +0,0 @@
# atakanozban.com
The source code for my personal portfolio website: an ASP.NET MVC 5 site with a small
custom admin panel for managing projects, affiliate links, social icons and the site
wallpaper, plus English/Turkish localization.
## Features
- Public site: home, about, my projects (with an image/iframe carousel per project),
affiliate links, dynamic social icons in the footer, and a changeable background wallpaper.
- Admin panel (`/admin`) protected by Forms Authentication:
- My Projects (bilingual posts + media carousel)
- Affiliate Links (bilingual cards with logos)
- Social Icons (footer icons)
- Wallpaper (site-wide background image)
- Optional TOTP two-factor authentication (Security page)
- My Projects: create/edit posts with EN/TR translations and a media manager
(upload images or embed iframes, drag-free reordering via a sort order field).
- Affiliate Links: create/edit links with a logo, title, description and EN/TR translations.
- Social Icons: manage the icon row shown in the site footer (Font Awesome classes + URLs).
- Wallpaper: upload a new background image for the whole site, or reset to the default.
- Security: optional TOTP two-factor authentication (Google Authenticator / Authy / etc.).
- Image uploads everywhere support both the regular file picker **and** pasting an
image straight from the clipboard (Ctrl+V).
- Public my-projects images open in a larger lightbox when clicked.
- English / Turkish localization via `.resx` resource files and a culture cookie.
## Tech stack
- ASP.NET MVC 5 / .NET Framework 4.7.2
- Entity Framework 6 (Code First, but with automatic migrations disabled — see
[Database setup](#database-setup) below)
- Microsoft SQL Server
- Bootstrap 5.3.3 and Font Awesome 6 (loaded from CDN)
## Prerequisites
- Windows with [Visual Studio 2019+](https://visualstudio.microsoft.com/) (the ".NET desktop
development" / "ASP.NET and web development" workload) or MSBuild + IIS/IIS Express
- SQL Server (LocalDB, SQL Server Express, or a full instance)
- (Optional) An FTP/hosting environment if you want to deploy it somewhere other than IIS Express
## Getting started
### 1. Clone and restore packages
```bash
git clone <this-repo-url>
cd atakanozbancom
```
Open `atakanozbancom.sln` in Visual Studio. NuGet packages will restore automatically on
build (or run `nuget restore atakanozbancom.sln` from a Developer Command Prompt).
### 2. Database setup
This project uses EF6 Code First **with `Database.SetInitializer<Context>(null)`**, meaning
Entity Framework will never create or alter tables for you — the schema is managed by hand.
1. Create an empty database (SQL Server Management Studio, Azure Data Studio, or `sqlcmd`):
```sql
CREATE DATABASE atakanozbancomdb;
```
2. Run [`database/schema.sql`](database/schema.sql) against that database. It creates every
table the app needs (admins, projects, affiliate links, social icons, site settings) and
inserts one seed admin account (`admin` / `change-this-password`).
3. **Change that seed password** (see [Security notes](#security-notes) — logins currently
compare passwords as plain text, so don't reuse a real password here).
### 3. Configure `Web.config`
Open `Web.config` and update the `connectionStrings` section to point at your SQL Server
instance:
```xml
<connectionStrings>
<add name="Context" connectionString="Data Source=YOUR_SERVER\INSTANCE;Initial Catalog=atakanozbancomdb;Integrated Security=true;" providerName="System.Data.SqlClient" />
</connectionStrings>
```
Then generate your own `<machineKey />` (see the comment above `<customErrors>` in
`Web.config` for a couple of ways to do this) and paste it into `<system.web>`. This keeps
authentication cookies and ViewState valid across app restarts/deployments — don't skip it,
and don't reuse a sample key you find online.
### 4. Run it
Press F5 in Visual Studio (or `Ctrl+F5` to skip the debugger). The site starts at the URL
shown in the IIS Express system tray icon / Visual Studio's toolbar. Log in at `/login`
with the admin account you created above, then manage content from `/admin`.
## Project structure
```
Controllers/ MVC controllers (public site + admin panel)
Models/classes/ Entity Framework models, view models, helpers
Views/ Razor views, organized by controller
App_GlobalResources/ English (.resx) and Turkish (.tr-TR.resx) translation strings
App_Start/ Routing, bundling and filter configuration
Content/uploads/ User-uploaded images (logos, project media, wallpaper) — gitignored
database/schema.sql Full database schema + seed admin account
web_atakanozbancom/ Static assets (CSS/JS/images) for the public site
```
## Uploaded content
Anything uploaded through the admin panel (project media, affiliate link logos, the
wallpaper) is written to folders under `Content/uploads/`. Those folders are tracked with
a `.gitkeep` placeholder but their contents are gitignored, since uploaded files are
runtime data, not source code.
## Security notes
This project was open-sourced as a personal portfolio/reference, not as a hardened
multi-tenant product. A few things worth knowing if you deploy your own copy or build on it:
- Optional **TOTP 2FA** and **password change** live under Admin → username menu → User Settings.
Existing installs can run `database/add-2fa.sql` then `database/harden-auth.sql`.
- Passwords are stored with **PBKDF2-SHA256** (`PasswordHasher`). On first successful login after
upgrade, any leftover plaintext password is rehashed automatically.
- **Passwords used to be stored in plain text.** If you fork an old clone, force a password change
and rotate machine keys before going live.
- The admin panel already includes `[Authorize]` on every management action and
`[ValidateAntiForgeryToken]` on every state-changing POST, so it's protected against CSRF
once you're logged in — but access is still gated by a single shared admin login rather
than per-user accounts/roles.
- File uploads are restricted to an image extension allowlist (`.jpg`, `.jpeg`, `.png`,
`.webp`, `.gif`) — SVG is intentionally excluded since it can carry executable script.
- User-entered descriptions are HTML-encoded before rendering; a small `[text](https://...)`
markdown-style syntax is the only way to add links, via `DescriptionFormatter`.
## License
MIT — see [LICENSE](LICENSE).
+1 -1
View File
@@ -199,7 +199,7 @@
<a class="nav-link fw-bold py-1 px-0" asp-controller="main" asp-action="ChangeLanguage" asp-route-lang="en" href="@Url.Action("index","main")">@atakanozbancom.App_GlobalResources.Resource.NavHome</a> <a class="nav-link fw-bold py-1 px-0" asp-controller="main" asp-action="ChangeLanguage" asp-route-lang="en" href="@Url.Action("index","main")">@atakanozbancom.App_GlobalResources.Resource.NavHome</a>
<a class="nav-link fw-bold py-1 px-0" asp-controller="main" asp-action="ChangeLanguage" asp-route-lang="en" aria-current="page" href="@Url.Action("index","about")">@atakanozbancom.App_GlobalResources.Resource.NavAboutMe</a> <a class="nav-link fw-bold py-1 px-0" asp-controller="main" asp-action="ChangeLanguage" asp-route-lang="en" aria-current="page" href="@Url.Action("index","about")">@atakanozbancom.App_GlobalResources.Resource.NavAboutMe</a>
<a class="nav-link fw-bold py-1 px-0" asp-controller="main" asp-action="ChangeLanguage" asp-route-lang="en" href="@Url.Action("index","myprojects")">@atakanozbancom.App_GlobalResources.Resource.NavMyProjects</a> <a class="nav-link fw-bold py-1 px-0" asp-controller="main" asp-action="ChangeLanguage" asp-route-lang="en" href="@Url.Action("index","myprojects")">@atakanozbancom.App_GlobalResources.Resource.NavMyProjects</a>
<a class="nav-link fw-bold py-1 px-0" asp-controller="main" asp-action="ChangeLanguage" asp-route-lang="en" href="@Url.Action("index","affiliate")">@atakanozbancom.App_GlobalResources.Resource.NavAffiliateLinks</a> @{ <a class="nav-link fw-bold py-1 px-0" asp-controller="main" asp-action="ChangeLanguage" asp-route-lang="en" href="@Url.Action("index","support")">@atakanozbancom.App_GlobalResources.Resource.NavAffiliateLinks</a> @{
var currentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture.Name; var currentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
var back = Request.RawUrl; var back = Request.RawUrl;
} }
+2 -3
View File
@@ -13,9 +13,8 @@
<system.web> <system.web>
<httpRuntime maxRequestLength="25600" executionTimeout="600" /> <httpRuntime maxRequestLength="25600" executionTimeout="600" />
<!-- Generate your own keys for production. Never commit real keys to a public repo. --> <machineKey validationKey="317412946B993448C673500FE9A36F856519ABFC991E4476091AE530AA2DCE25BDFCF3E9251279FA95F898D84AAD6656B68127FC8EA62B1487EBD07D1B4A5653"
<machineKey validationKey="REPLACE_WITH_YOUR_VALIDATION_KEY" decryptionKey="33D1829CBAA044E19E39B70253390BB66DBCB59FCC8946F3EA122F87A289EE85"
decryptionKey="REPLACE_WITH_YOUR_DECRYPTION_KEY"
validation="HMACSHA256" validation="HMACSHA256"
decryption="AES" /> decryption="AES" />
<customErrors mode="RemoteOnly" /> <customErrors mode="RemoteOnly" />
+2 -2
View File
@@ -156,7 +156,7 @@
<Compile Include="App_Start\RouteConfig.cs" /> <Compile Include="App_Start\RouteConfig.cs" />
<Compile Include="Controllers\aboutController.cs" /> <Compile Include="Controllers\aboutController.cs" />
<Compile Include="Controllers\adminController.cs" /> <Compile Include="Controllers\adminController.cs" />
<Compile Include="Controllers\affiliateController.cs" /> <Compile Include="Controllers\supportController.cs" />
<Compile Include="Controllers\loginController.cs" /> <Compile Include="Controllers\loginController.cs" />
<Compile Include="Controllers\mainController.cs" /> <Compile Include="Controllers\mainController.cs" />
<Compile Include="Controllers\languageController.cs" /> <Compile Include="Controllers\languageController.cs" />
@@ -264,7 +264,7 @@
<Content Include="Views\login\verify.cshtml" /> <Content Include="Views\login\verify.cshtml" />
<Content Include="Views\Shared\_PortfolioLayout.cshtml" /> <Content Include="Views\Shared\_PortfolioLayout.cshtml" />
<Content Include="Views\about\index.cshtml" /> <Content Include="Views\about\index.cshtml" />
<Content Include="Views\affiliate\index.cshtml" /> <Content Include="Views\support\index.cshtml" />
<Content Include="Views\main\index.cshtml" /> <Content Include="Views\main\index.cshtml" />
<Content Include="Views\main\socialicons.cshtml" /> <Content Include="Views\main\socialicons.cshtml" />
<Content Include="Views\main\wallpaper.cshtml" /> <Content Include="Views\main\wallpaper.cshtml" />
-117
View File
@@ -1,117 +0,0 @@
-- atakanozban.com - full database schema
-- Target: Microsoft SQL Server
--
-- This project uses EF6 Code First with Database.SetInitializer(null), which means
-- Entity Framework will NOT create or migrate the database automatically. Run this
-- script once against a fresh database to create every table the application needs.
--
-- Usage:
-- 1. Create an empty database, e.g.:
-- CREATE DATABASE atakanozbancomdb;
-- 2. Run this whole script against that database (SSMS, Azure Data Studio, sqlcmd, etc.)
USE atakanozbancomdb;
GO
-- =========================================================
-- Admin users (used for the /login and /admin panel)
-- =========================================================
CREATE TABLE dbo.admins (
id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
username NVARCHAR(100) NOT NULL,
password NVARCHAR(255) NOT NULL,
two_factor_enabled BIT NOT NULL CONSTRAINT DF_admins_two_factor_enabled DEFAULT(0),
two_factor_secret NVARCHAR(512) NULL,
login_failed_count INT NOT NULL CONSTRAINT DF_admins_login_failed_count DEFAULT(0),
login_lock_until DATETIME2 NULL,
totp_failed_count INT NOT NULL CONSTRAINT DF_admins_totp_failed_count DEFAULT(0),
totp_lock_until DATETIME2 NULL
);
GO
-- =========================================================
-- My Projects
-- =========================================================
CREATE TABLE dbo.myprojects (
id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
image NVARCHAR(MAX) NULL, -- legacy, kept for backward compatibility
iframe NVARCHAR(MAX) NULL, -- legacy, kept for backward compatibility
title NVARCHAR(255) NULL,
description NVARCHAR(MAX) NULL
);
GO
CREATE TABLE dbo.myprojectstranslations (
id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
project_id INT NOT NULL,
culture NVARCHAR(10) NOT NULL,
title NVARCHAR(255) NULL,
description NVARCHAR(MAX) NULL,
CONSTRAINT FK_myprojectstranslations_myprojects
FOREIGN KEY (project_id) REFERENCES dbo.myprojects(id) ON DELETE CASCADE
);
GO
CREATE TABLE dbo.projectmedia (
id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
projectid INT NOT NULL,
mediatype INT NOT NULL, -- 1 = Image, 2 = Iframe
mediaurl NVARCHAR(1000) NOT NULL,
sortorder INT NOT NULL DEFAULT 0,
CONSTRAINT FK_projectmedia_myprojects
FOREIGN KEY (projectid) REFERENCES dbo.myprojects(id) ON DELETE CASCADE
);
GO
-- =========================================================
-- Affiliate Links
-- =========================================================
CREATE TABLE dbo.affiliatelinks (
id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
image NVARCHAR(500) NOT NULL,
title NVARCHAR(200) NOT NULL,
description NVARCHAR(500) NULL,
url NVARCHAR(1000) NOT NULL,
sortorder INT NOT NULL DEFAULT 0
);
GO
CREATE TABLE dbo.affiliatelinktranslations (
id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
affiliatelink_id INT NOT NULL,
culture NVARCHAR(10) NOT NULL,
title NVARCHAR(255) NULL,
description NVARCHAR(MAX) NULL,
CONSTRAINT FK_affiliatelinktranslations_affiliatelinks
FOREIGN KEY (affiliatelink_id) REFERENCES dbo.affiliatelinks(id) ON DELETE CASCADE
);
GO
-- =========================================================
-- Social Icons (footer icon row)
-- =========================================================
CREATE TABLE dbo.socialicons (
id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
icon NVARCHAR(100) NOT NULL, -- Font Awesome class(es), e.g. "fa-brands fa-github"
url NVARCHAR(500) NOT NULL,
sortorder INT NOT NULL DEFAULT 0
);
GO
-- =========================================================
-- Site Settings (currently just the wallpaper; single row table)
-- =========================================================
CREATE TABLE dbo.sitesettings (
id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
wallpaper NVARCHAR(500) NULL -- public URL of the current wallpaper; NULL = use bundled default
);
GO
-- =========================================================
-- Seed data: one admin account so you can log in to /admin
-- IMPORTANT: change the username/password below before/after running this,
-- and see the "Security notes" section in README.md - passwords are currently
-- stored and compared in plain text, so pick something you don't reuse elsewhere.
-- =========================================================
INSERT INTO dbo.admins (username, password) VALUES ('admin', 'change-this-password');
GO
+4
View File
@@ -79,6 +79,10 @@ body {
border: 0; border: 0;
} }
/*
* Affiliate links
*/
.affiliate-list { .affiliate-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;