diff --git a/CatherineLynwood/Controllers/DiscoveryController.cs b/CatherineLynwood/Controllers/DiscoveryController.cs index 03afcd7..5ec5228 100644 --- a/CatherineLynwood/Controllers/DiscoveryController.cs +++ b/CatherineLynwood/Controllers/DiscoveryController.cs @@ -1,12 +1,16 @@ -using System.Globalization; -using System.Security.Cryptography; -using System.Text.RegularExpressions; -using System.Text; -using CatherineLynwood.Models; +using CatherineLynwood.Models; using CatherineLynwood.Services; + using Microsoft.AspNetCore.Mvc; + using Newtonsoft.Json; +using System.Globalization; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; + namespace CatherineLynwood.Controllers { [Route("the-alpha-flame/discovery")] @@ -48,12 +52,21 @@ namespace CatherineLynwood.Controllers Reviews reviews = await _dataAccess.GetReviewsAsync(); reviews.SchemaJsonLd = GenerateBookSchemaJsonLd(reviews, 3); + string src = variant switch + { + Variant.A => "DiscoveryA", + Variant.B => "DiscoveryB", + _ => "DiscoveryC" + }; + + // VM var vm = new DiscoveryPageViewModel { Reviews = reviews, UserIso2 = iso2, - Buy = buyLinks + Buy = buyLinks, + Src = src }; // View mapping: @@ -89,6 +102,23 @@ namespace CatherineLynwood.Controllers [Route("chapters/chapter-13-susie")] public IActionResult Chapter13() => View(); + [Route("trailer")] + public async Task Trailer() + { + // Country ISO2 + var iso2 = (_country.Iso2 ?? "UK").ToUpperInvariant(); + if (iso2 == "GB") iso2 = "UK"; + + FlagSupportViewModel flagSupportViewModel = await _dataAccess.GetTrailerLikes(); + + foreach (var flag in flagSupportViewModel.FlagCounts) + { + flag.Selected = flag.Key.ToLower() == iso2.ToLower(); + } + + return View(flagSupportViewModel); + } + [BookAccess(1, 1)] [Route("extras")] public IActionResult Extras() => View(); diff --git a/CatherineLynwood/Middleware/GeoResolutionMiddleware.cs b/CatherineLynwood/Middleware/GeoResolutionMiddleware.cs index ccc34c2..dac225c 100644 --- a/CatherineLynwood/Middleware/GeoResolutionMiddleware.cs +++ b/CatherineLynwood/Middleware/GeoResolutionMiddleware.cs @@ -28,6 +28,7 @@ namespace CatherineLynwood.Middleware _logger.LogInformation("GeoMW: path={Path}", context.Request.Path); var ip = GetClientIp(context); + var userAgent = context.Request.Headers["User-Agent"].ToString(); // In Development, replace loopback with a known public IP so ResolveAsync definitely runs if (ip is null || IPAddress.IsLoopback(ip)) @@ -50,7 +51,7 @@ namespace CatherineLynwood.Middleware _logger.LogInformation("GeoMW: calling resolver for {IP}", ip); - var geo = await _resolver.ResolveAsync(ip); + var geo = await _resolver.ResolveAsync(ip, userAgent); if (geo is not null) { diff --git a/CatherineLynwood/Models/DIscoveryPageViewModel.cs b/CatherineLynwood/Models/DIscoveryPageViewModel.cs index c05259f..99821f7 100644 --- a/CatherineLynwood/Models/DIscoveryPageViewModel.cs +++ b/CatherineLynwood/Models/DIscoveryPageViewModel.cs @@ -11,6 +11,8 @@ public string UserIso2 { get; set; } = "GB"; + public string Src = "Discovery"; + #endregion Public Properties } } \ No newline at end of file diff --git a/CatherineLynwood/Models/GeoIpResult.cs b/CatherineLynwood/Models/GeoIpResult.cs index 7e6e76d..fa110e6 100644 --- a/CatherineLynwood/Models/GeoIpResult.cs +++ b/CatherineLynwood/Models/GeoIpResult.cs @@ -14,6 +14,8 @@ public string Source { get; set; } + public string UserAgent { get; set; } + #endregion Public Properties } } \ No newline at end of file diff --git a/CatherineLynwood/Services/DataAccess.cs b/CatherineLynwood/Services/DataAccess.cs index 030ef43..b309bcd 100644 --- a/CatherineLynwood/Services/DataAccess.cs +++ b/CatherineLynwood/Services/DataAccess.cs @@ -610,7 +610,8 @@ namespace CatherineLynwood.Services Iso2 = GetDataString(rdr, "ISO2"), CountryName = GetDataString(rdr, "CountryName"), LastSeenUtc = GetDataDate(rdr, "LastSeenUtc"), - Source = GetDataString(rdr, "Source") + Source = GetDataString(rdr, "Source"), + UserAgent = GetDataString(rdr, "UserAgent") }; } } @@ -1065,6 +1066,7 @@ namespace CatherineLynwood.Services cmd.Parameters.AddWithValue("@CountryName", geo.CountryName); cmd.Parameters.AddWithValue("@LastSeenUtc", geo.LastSeenUtc); cmd.Parameters.AddWithValue("@Source", geo.Source ?? (object)DBNull.Value); + cmd.Parameters.AddWithValue("@UserAgent", geo.UserAgent ?? (object)DBNull.Value); await cmd.ExecuteNonQueryAsync(); } diff --git a/CatherineLynwood/Services/GeoResolver.cs b/CatherineLynwood/Services/GeoResolver.cs index 102baf9..47f7793 100644 --- a/CatherineLynwood/Services/GeoResolver.cs +++ b/CatherineLynwood/Services/GeoResolver.cs @@ -23,7 +23,7 @@ namespace CatherineLynwood.Services _logger = logger; } - public async Task ResolveAsync(IPAddress ip, CancellationToken ct = default) + public async Task ResolveAsync(IPAddress ip, string userAgent, CancellationToken ct = default) { var ipStr = ip.ToString(); @@ -56,7 +56,8 @@ namespace CatherineLynwood.Services Iso2 = iso2, CountryName = country ?? "", LastSeenUtc = DateTime.UtcNow, - Source = "ip-api" + Source = "ip-api", + UserAgent = userAgent }; await _dataAccess.SaveGeoIpAsync(geo); diff --git a/CatherineLynwood/Services/IGeoResolver.cs b/CatherineLynwood/Services/IGeoResolver.cs index 2f468ac..0faacf0 100644 --- a/CatherineLynwood/Services/IGeoResolver.cs +++ b/CatherineLynwood/Services/IGeoResolver.cs @@ -6,6 +6,6 @@ namespace CatherineLynwood.Services { public interface IGeoResolver { - Task ResolveAsync(IPAddress ip, CancellationToken ct = default); + Task ResolveAsync(IPAddress ip, string userAgent, CancellationToken ct = default); } } diff --git a/CatherineLynwood/Views/Discovery/IndexMobileC.cshtml b/CatherineLynwood/Views/Discovery/IndexMobileC.cshtml new file mode 100644 index 0000000..ac15614 --- /dev/null +++ b/CatherineLynwood/Views/Discovery/IndexMobileC.cshtml @@ -0,0 +1,605 @@ +@model CatherineLynwood.Models.DiscoveryPageViewModel + +@{ + ViewData["Title"] = "The Alpha Flame: A Gritty 1980s Birmingham Crime Novel about Twin Sisters"; + bool showReviews = Model.Reviews.Items.Any(); +} + +
+
+ +
+
+ + +
+
+
+ + + + + + + + +
+ + +
+
+ + + + + + + + +
+

The story starts here.

+ Buy now +
+
+
+ + +
+
+
+

The Alpha Flame: Discovery

+

Survival, secrets, and sisters in 1983 Birmingham.

+
+ +
+ +
+
+ +
+
+
+ +
+

Listen to Catherine talking about the book

+
+
+ + +

+ Set in 1983 Birmingham, nearby Redditch, and Barmouth in Wales, The Alpha Flame: Discovery follows two young women, Beth and Maggie, as they uncover dark family secrets and fight to survive. Gritty and emotionally charged, it explores the bond between two women who refuse to be broken. +

+ + +
+
+

For Beth, the world is a cold and unforgiving place. Devastation strikes in a single moment, leaving her isolated, shattered, and vulnerable. Alone in the bleak shadows of a city that offers neither refuge nor redemption, she is forced to navigate a relentless cycle of desperation and despair. Every step of her journey tests the limits of her endurance, pushing her into harrowing situations where survival feels like a hollow victory. Beth’s existence is marked by loss, betrayal, and an almost suffocating loneliness that threatens to consume her entirely. Yet, even in the darkest corners of her ordeal, a fragile ember of defiance smoulders within her, a quiet, stubborn refusal to let the world destroy her completely.

+

Maggie, by contrast, is a force of nature, a woman who thrives on her unshakable drive and an unrelenting belief in her own power. Behind her fiery red hair and disarming charm lies a storm of determination and ferocity. Maggie doesn’t just live; she races through life, fuelled by a need for speed and the thrill of freedom. Her Triumph TR6 isn’t just a car; it’s an extension of her spirit, sleek, powerful, and unapologetically bold. On the open road, with the engine roaring and the world blurring past her, she feels invincible. But Maggie’s intensity doesn’t stop at the wheel. Her relationships burn just as brightly. As a lover, she is dominant, passionate, and unafraid to embrace her darker desires. While fiercely loving and loyal, Maggie is also formidable; crossing her isn’t a mistake anyone makes twice.

+

When fate brings Beth and Maggie together, their connection is explosive, a union of two polar opposites that burns with both tenderness and raw power. For Beth, Maggie represents a lifeline, a reminder that love and trust still exist, even in a world that has betrayed her at every turn. For Maggie, Beth awakens a fierce protectiveness and vulnerability she’s rarely allowed herself to feel. Together, they ignite a flame that challenges them to confront their own fears, desires, and limitations.

+

Set against the kaleidoscope of 1983, where synthesised anthems provide a pulsing soundtrack and the streets are alive with the bold styles and rebellious energy of the decade, their story unfolds in a city teeming with danger and intrigue. From high speed chases along winding roads to dimly lit clubs and desolate alleyways, the heroines’ journey is a visceral exploration of survival and freedom. The neon haze of the era contrasts sharply with the stark realities they face, painting a vivid picture of a world where strength and vulnerability coexist.

+

As secrets surface and danger tightens its grip, Beth and Maggie must confront not only the challenges around them but the truths within themselves. Their bond is tested by betrayal, desire, and the shadows of their pasts, but through it all, their flame burns brighter, illuminating their courage and the unbreakable spirit of two heroines determined to rewrite their fates.

+

At its heart, The Alpha Flame is a story of survival, passion, and empowerment. It captures the raw power of human connection against the gritty, vibrant backdrop of an unforgettable era. With its blend of drama, intensity, and unapologetic emotion, this is a story that will leave its mark long after the final page.

+
+
+
+
+
+ + +@if (showReviews) +{ + var top = Model.Reviews.Items.Where(x => x.RatingValue == 5).OrderByDescending(y => y.DatePublished).First(); + var fullStars = (int)Math.Floor(top.RatingValue); + var hasHalfStar = top.RatingValue - fullStars >= 0.5; + var emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0); + var reviewDate = top.DatePublished.ToString("d MMMM yyyy"); + +
+
+
+

★ Reader Praise ★

+
+ + @for (int i = 0; i < fullStars; i++) + { + + } + @if (hasHalfStar) + { + + } + @for (int i = 0; i < emptyStars; i++) + { + + } + + @Html.Raw(top.ReviewBody) +
+ @top.AuthorName on + + @if (string.IsNullOrEmpty(top.URL)) + { + @top.SiteName + } + else + { + @top.SiteName + } + + , @reviewDate +
+
+ @if (Model.Reviews.Items.Count > 1) + { + + } +
+
+
+} + + +
+ +
+ + + + + +
+

Chapter Previews

+
+
+
+ + + +
+

Chapter 1, Drowning in Silence, Beth

+

Beth returns home to find her mother lifeless in the bath...

+ +
+
+
+
+
+ + + +
+

Chapter 2, The Last Lesson, Maggie

+

On Christmas Eve, Maggie nervously heads out for her driving test not knowing the story that will pan out before her...

+ +
+
+
+
+
+ + + +
+

Chapter 13, A Name She Never Owned, Susie

+

Susie goes out for a drink with a punter. What on earth could go wrong...

+ +
+
+
+
+
+ +@section Scripts { + + + + + + + +} + +@section Meta { + + + +} diff --git a/CatherineLynwood/Views/Discovery/_BuyBox.cshtml b/CatherineLynwood/Views/Discovery/_BuyBox.cshtml index 5580477..cf6e5cf 100644 --- a/CatherineLynwood/Views/Discovery/_BuyBox.cshtml +++ b/CatherineLynwood/Views/Discovery/_BuyBox.cshtml @@ -40,7 +40,7 @@
Hardback, direct @if (!string.IsNullOrWhiteSpace(L.IngramHardbackPrice)) @@ -55,7 +55,7 @@
Paperback, direct @if (!string.IsNullOrWhiteSpace(L.IngramPaperbackPrice)) @@ -82,7 +82,7 @@
Hardback, Amazon @@ -90,7 +90,7 @@
Paperback, Amazon @@ -110,7 +110,7 @@
Hardback, @(L.NationalLabel ?? "National") @@ -121,7 +121,7 @@
Paperback, @(L.NationalLabel ?? "National") @@ -142,7 +142,7 @@
Apple Books @@ -150,7 +150,7 @@
Kobo @@ -158,7 +158,7 @@
Kindle diff --git a/CatherineLynwood/Views/Shared/_Layout.cshtml b/CatherineLynwood/Views/Shared/_Layout.cshtml index 967ddf2..34dd0e8 100644 --- a/CatherineLynwood/Views/Shared/_Layout.cshtml +++ b/CatherineLynwood/Views/Shared/_Layout.cshtml @@ -11,34 +11,6 @@ - - - - -