Skip to content

Commit

Permalink
Add questions
Browse files Browse the repository at this point in the history
  • Loading branch information
mythz committed Mar 14, 2024
1 parent 1a34ea2 commit 50cc5fc
Show file tree
Hide file tree
Showing 16 changed files with 648 additions and 62 deletions.
58 changes: 58 additions & 0 deletions MyApp.ServiceInterface/Data/DbExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using MyApp.ServiceModel;
using ServiceStack;
using ServiceStack.OrmLite;
using ServiceStack.Text;

namespace MyApp.Data;

public static class DbExtensions
{
public static SqlExpression<Post> WhereContainsTag(this SqlExpression<Post> q, string? tag)
{
if (tag != null)
{
tag = tag.UrlDecode().Replace("'","").Replace("\\","").SqlVerifyFragment();
q.UnsafeWhere("',' || tags || ',' like '%," + tag + ",%'");
}
return q;
}

public static SqlExpression<Post> WhereSearch(this SqlExpression<Post> q, string? search)
{
if (!string.IsNullOrEmpty(search))
{
search = search.Trim();
if (search.StartsWith('[') && search.EndsWith(']'))
{
q.WhereContainsTag(search.TrimStart('[').TrimEnd(']'));
}
else
{
var sb = StringBuilderCache.Allocate();
var words = search.Split(' ');
for (var i = 0; i < words.Length; i++)
{
if (sb.Length > 0)
sb.Append(" AND ");
sb.AppendLine("(title like '%' || {" + i + "} || '%' or summary like '%' || {" + i + "} || '%' or tags like '%' || {" + i + "} || '%')");
}

var sql = StringBuilderCache.ReturnAndFree(sb);
q.UnsafeWhere(sql, words.Cast<object>().ToArray());
}
}
return q;
}

public static SqlExpression<Post> OrderByView(this SqlExpression<Post> q, string? view)
{
view = view?.ToLower();
if (view is "popular" or "most-views")
q.OrderByDescending(x => x.ViewCount);
else if (view is "interesting" or "most-votes")
q.OrderByDescending(x => x.Score);
else
q.OrderByDescending(x => x.Id);
return q;
}
}
7 changes: 7 additions & 0 deletions MyApp.ServiceModel/RenderComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@

namespace MyApp.ServiceModel;

public class RenderHome
{
public string? Tab { get; set; }
public List<Post> Posts { get; set; }

Check warning on line 8 in MyApp.ServiceModel/RenderComponent.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Posts' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
}

public class RenderComponent : IReturnVoid
{
public int? IfQuestionModified { get; set; }
public QuestionAndAnswers? Question { get; set; }
public RenderHome? Home { get; set; }
}
49 changes: 33 additions & 16 deletions MyApp/Components/Pages/Home.razor
Original file line number Diff line number Diff line change
@@ -1,26 +1,43 @@
@page "/"
@inject RendererCache RendererCache
@inject IMessageProducer MessageProducer
@inject IDbConnectionFactory DbFactory

<PageTitle>Home</PageTitle>

<div class="mt-8 mb-20 mx-auto max-w-5xl px-4">
<div class="mb-4">
<h1 class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100 sm:text-5xl">
<span class="block xl:inline">Top Questions</span>
</h1>
</div>

<QuestionPosts Posts=@Posts />
</div>
<PageTitle>pvq.app</PageTitle>

@if (Html != null)
{
@BlazorHtml.Raw(Html)
}
else
{
<HomeTab Tab=@Tab Posts=@Posts />
}

@code {
string? Html;

[SupplyParameterFromQuery]
string? Tab { get; set; }

List<Post> Posts { get; set; } = [];

protected override void OnInitialized()
protected override async Task OnInitializedAsync()
{
using var db = HostContext.AppHost.GetDbConnection();
Posts = db.Select(db.From<Post>()
.OrderByDescending(x => x.Score)
.Take(50));
Html = await RendererCache.GetHomeTabHtmlAsync(Tab);
if (Html != null)
return;

using var db = await DbFactory.OpenAsync();
var q = db.From<Post>();
q.OrderByView(Tab);
Posts = await db.SelectAsync(q.Take(50));

MessageProducer.Publish(new RenderComponent {
Home = new() {
Tab = Tab,
Posts = Posts,
}
});
}
}
113 changes: 113 additions & 0 deletions MyApp/Components/Pages/Questions/Index.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
@page "/questions"
@inject IDbConnectionFactory DbFactory

<PageTitle>@Title</PageTitle>

@if (posts != null)
{
<div class="mt-8 mb-20 mx-auto max-w-5xl px-4">
<div class="mb-4">
<h1 class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100 sm:text-5xl">
<span class="block xl:inline">@Title</span>
</h1>
</div>


@if (posts.Count > 0)
{
<div class="py-2 flex justify-end">
<QuestionViewTabs Path=@Path Tabs=@Tabs Active=@Tab/>
</div>

@if (total > PageSize)
{
<PagesNav class="border-b" Path=@Path PageSize=@PageSize Total=@total Page=@Math.Max(1, Page ?? 1) />
}

<QuestionPosts Posts=@posts />

@if (total > PageSize)
{
<PagesNav class="border-t" Path=@Path PageSize=@PageSize Total=@total Page=@Math.Max(1, Page ?? 1) />
}
}
else
{
<div class="mt-8 text-lg">
This search return no results.
</div>
}
</div>
}
else
{
<div class="mt-3 mb-20 mx-auto max-w-fit">
@if (error != null)
{
<ErrorSummary Status=@error />
}
else
{
<Loading />
}
</div>
}

@code {
string Path => "/questions".AddQueryParam("q", Q);
int? Skip => Page > 1 ? (Page - 1) * PageSize : 0;
string Title => "All Questions" + (!string.IsNullOrEmpty(Q) ? $" with '{Q}'" : "");

static string[] Tabs = ["most-votes", "most-views", "most-recent"];

[SupplyParameterFromQuery]
string? Q { get; set; }

[SupplyParameterFromQuery]
string? Tab { get; set; }

[SupplyParameterFromQuery]
int? Page { get; set; }

[SupplyParameterFromQuery]
int? PageSize { get; set; }

List<Post>? posts;
ResponseStatus? error = null;
long? total;

async Task Load()
{
try
{
if (Tab == null || !Tabs.Contains(Tab))
Tab = Tabs[0];
if (PageSize is null or <= 0)
PageSize = 25;

using var db = await DbFactory.OpenAsync();
var q = db.From<Post>();

q.OrderByView(Tab);
if (!string.IsNullOrEmpty(Q))
{
q.WhereSearch(Q);
}

posts = await db.SelectAsync(q
.OrderByView(Tab)
.Skip(Page > 1 ? (Page - 1) * PageSize : 0)
.Take(PageSize.ToPageSize()));

total = db.Count(q);
}
catch (Exception ex)
{
error = ex.ToResponseStatus();
}
}

protected override Task OnInitializedAsync() => Load();

protected override Task OnParametersSetAsync() => Load();
}
27 changes: 18 additions & 9 deletions MyApp/Components/Pages/Questions/Question.razor
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
@page "/questions/{Id:int}/{*Slug}"
@using ServiceStack.IO
@using MyApp.Data
@inject R2VirtualFiles R2
@inject RendererCache RendererCache
@inject NavigationManager NavigationManager
@inject ServiceStack.Messaging.IMessageProducer MessageProducer
@inject IMessageProducer MessageProducer

<PageTitle>@title</PageTitle>

Expand Down Expand Up @@ -42,7 +40,7 @@ else
AuthorInfo? author;

string title = "";
string? Html = null;
string? Html;

async Task load()
{
Expand All @@ -51,11 +49,22 @@ else

if (!string.IsNullOrEmpty(Html))
{
var attrPrefix = "data-title=\"";
title = Html.IndexOf(attrPrefix, StringComparison.Ordinal) >= 0
? Html.RightPart(attrPrefix).LeftPart('"')
: title;
var attrPrefix = "<template id=\"Post\">";
var json = Html.IndexOf(attrPrefix, StringComparison.Ordinal) >= 0
? Html.RightPart(attrPrefix).LeftPart("</template>")
: null;

if (json != null)
{
var post = json.FromJson<Post>();
var slug = post.Slug.GenerateSlug(200);
if (slug != Slug)
{
NavigationManager.NavigateTo($"/questions/{Id}/{slug}");
return;
}
}

MessageProducer.Publish(new RenderComponent {
IfQuestionModified = Id
});
Expand All @@ -77,7 +86,7 @@ else

MessageProducer.Publish(new RenderComponent { Question = question });

var slug = question.Post.Slug.GenerateSlug();
var slug = question.Post.Slug.GenerateSlug(200);
if (slug != Slug)
{
NavigationManager.NavigateTo($"/questions/{Id}/{slug}");
Expand Down
Loading

0 comments on commit 50cc5fc

Please sign in to comment.