Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e97a71e5b2 | ||
|
|
8ec6e3fd50 | ||
|
|
0ccbef45a1 |
@@ -361,3 +361,18 @@ 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/
|
||||||
|
Before Width: | Height: | Size: 372 KiB |
@@ -115,7 +115,7 @@ namespace atakanozbancom.App_GlobalResources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Support my work 💙.
|
/// Looks up a localized string similar to Affiliate Links.
|
||||||
/// </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 Support my work 💙.
|
/// Looks up a localized string similar to Affiliate Links.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string NavAffiliateLinks {
|
public static string NavAffiliateLinks {
|
||||||
get {
|
get {
|
||||||
|
|||||||
@@ -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>Support my work 💙</value>
|
<value>Affiliate Links</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AffiliateLinksTitle" xml:space="preserve">
|
<data name="AffiliateLinksTitle" xml:space="preserve">
|
||||||
<value>Support my work 💙</value>
|
<value>Affiliate Links</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>
|
||||||
|
|||||||
@@ -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>Çalışmamı Destekleyin</value>
|
<value>Referans Linklerim</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AffiliateLinksTitle" xml:space="preserve">
|
<data name="AffiliateLinksTitle" xml:space="preserve">
|
||||||
<value>Çalışmamı Destekleyin</value>
|
<value>Affiliate Linklerim</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>
|
||||||
|
|||||||
@@ -13,14 +13,6 @@ 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}",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 854 B |
|
Before Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 3.5 MiB |
|
Before Width: | Height: | Size: 986 KiB |
|
Before Width: | Height: | Size: 722 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 526 KiB |
|
Before Width: | Height: | Size: 372 KiB |
|
Before Width: | Height: | Size: 372 KiB |
|
Before Width: | Height: | Size: 372 KiB |
|
Before Width: | Height: | Size: 372 KiB |
|
Before Width: | Height: | Size: 372 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 544 KiB |
|
Before Width: | Height: | Size: 372 KiB |
|
Before Width: | Height: | Size: 5.5 MiB |
@@ -10,11 +10,10 @@ using atakanozbancom.Models.classes;
|
|||||||
|
|
||||||
namespace atakanozbancom.Controllers
|
namespace atakanozbancom.Controllers
|
||||||
{
|
{
|
||||||
public class SupportController : Controller
|
public class AffiliateController : Controller
|
||||||
{
|
{
|
||||||
private readonly Context db = new Context();
|
private readonly Context db = new Context();
|
||||||
|
|
||||||
// GET: /Support
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public ActionResult Index()
|
public ActionResult Index()
|
||||||
{
|
{
|
||||||
@@ -57,7 +56,6 @@ namespace atakanozbancom.Controllers
|
|||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: /Support/ChangeLanguage
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public ActionResult ChangeLanguage(string lang)
|
public ActionResult ChangeLanguage(string lang)
|
||||||
{
|
{
|
||||||
@@ -62,7 +62,6 @@ 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
|
||||||
@@ -77,7 +76,6 @@ 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))
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ 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
|
||||||
{
|
{
|
||||||
@@ -15,20 +13,10 @@ 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()
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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.
|
||||||
@@ -32,10 +32,8 @@ 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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ 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>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,8 @@ 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);
|
||||||
@@ -23,12 +14,8 @@ 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;
|
||||||
@@ -36,7 +23,6 @@ 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 />");
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ 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>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ 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; }
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ 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; }
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
# 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).
|
||||||
@@ -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","support")">@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","affiliate")">@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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,9 @@
|
|||||||
|
|
||||||
<system.web>
|
<system.web>
|
||||||
<httpRuntime maxRequestLength="25600" executionTimeout="600" />
|
<httpRuntime maxRequestLength="25600" executionTimeout="600" />
|
||||||
<machineKey validationKey="317412946B993448C673500FE9A36F856519ABFC991E4476091AE530AA2DCE25BDFCF3E9251279FA95F898D84AAD6656B68127FC8EA62B1487EBD07D1B4A5653"
|
<!-- Generate your own keys for production. Never commit real keys to a public repo. -->
|
||||||
decryptionKey="33D1829CBAA044E19E39B70253390BB66DBCB59FCC8946F3EA122F87A289EE85"
|
<machineKey validationKey="REPLACE_WITH_YOUR_VALIDATION_KEY"
|
||||||
|
decryptionKey="REPLACE_WITH_YOUR_DECRYPTION_KEY"
|
||||||
validation="HMACSHA256"
|
validation="HMACSHA256"
|
||||||
decryption="AES" />
|
decryption="AES" />
|
||||||
<customErrors mode="RemoteOnly" />
|
<customErrors mode="RemoteOnly" />
|
||||||
|
|||||||
@@ -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\supportController.cs" />
|
<Compile Include="Controllers\affiliateController.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\support\index.cshtml" />
|
<Content Include="Views\affiliate\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" />
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
-- 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
|
||||||
@@ -79,10 +79,6 @@ body {
|
|||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Affiliate links
|
|
||||||
*/
|
|
||||||
|
|
||||||
.affiliate-list {
|
.affiliate-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||