Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc45c35dbe | ||
|
|
202dc16a52 | ||
|
|
cf37f33a1e |
@@ -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/
|
|
||||||
|
After Width: | Height: | Size: 372 KiB |
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}",
|
||||||
|
|||||||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 854 B |
|
After Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 3.5 MiB |
|
After Width: | Height: | Size: 986 KiB |
|
After Width: | Height: | Size: 722 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 526 KiB |
|
After Width: | Height: | Size: 372 KiB |
|
After Width: | Height: | Size: 372 KiB |
|
After Width: | Height: | Size: 372 KiB |
|
After Width: | Height: | Size: 372 KiB |
|
After Width: | Height: | Size: 372 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 544 KiB |
|
After Width: | Height: | Size: 372 KiB |
|
After Width: | Height: | Size: 5.5 MiB |
@@ -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)
|
||||||
{
|
{
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 />");
|
||||||
|
|||||||
@@ -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>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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).
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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;
|
||||||
|
|||||||