Skip to content

Commit 45445e1

Browse files
committed
stuff
1 parent dcdb852 commit 45445e1

17 files changed

+657
-0
lines changed

LunchTodayApi/.idea/.idea.LunchTodayApi/.idea/.gitignore

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

LunchTodayApi/.idea/.idea.LunchTodayApi/.idea/indexLayout.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

LunchTodayApi/.idea/.idea.LunchTodayApi/.idea/vcs.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

LunchTodayApi/LunchTodayApi.sln

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LunchTodayApi", "LunchTodayApi\LunchTodayApi.csproj", "{2BAAAA8F-8FB2-416C-9875-0B8E570D2958}"
4+
EndProject
5+
Global
6+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7+
Debug|Any CPU = Debug|Any CPU
8+
Release|Any CPU = Release|Any CPU
9+
EndGlobalSection
10+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
11+
{2BAAAA8F-8FB2-416C-9875-0B8E570D2958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
12+
{2BAAAA8F-8FB2-416C-9875-0B8E570D2958}.Debug|Any CPU.Build.0 = Debug|Any CPU
13+
{2BAAAA8F-8FB2-416C-9875-0B8E570D2958}.Release|Any CPU.ActiveCfg = Release|Any CPU
14+
{2BAAAA8F-8FB2-416C-9875-0B8E570D2958}.Release|Any CPU.Build.0 = Release|Any CPU
15+
EndGlobalSection
16+
EndGlobal
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
3+
namespace LunchTodayApi.Controllers;
4+
5+
[ApiController]
6+
[Route("[controller]")]
7+
public class WeatherForecastController : ControllerBase
8+
{
9+
private static readonly string[] Summaries = new[]
10+
{
11+
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
12+
};
13+
14+
private readonly ILogger<WeatherForecastController> _logger;
15+
16+
public WeatherForecastController(ILogger<WeatherForecastController> logger)
17+
{
18+
_logger = logger;
19+
}
20+
21+
[HttpGet(Name = "GetWeatherForecast")]
22+
public IEnumerable<WeatherForecast> Get()
23+
{
24+
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
25+
{
26+
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
27+
TemperatureC = Random.Shared.Next(-20, 55),
28+
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
29+
})
30+
.ToArray();
31+
}
32+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
using System.Net;
2+
using System.Text.RegularExpressions;
3+
using CSharpFunctionalExtensions;
4+
using ScrapySharp.Extensions;
5+
using ScrapySharp.Network;
6+
7+
namespace LunchTodayApi.LunchMenuFinder;
8+
9+
public class RestaurantCrawler : IRestaurantCrawler
10+
{
11+
private readonly ScrapingBrowser _browser = new();
12+
private static readonly List<string> SwedishWeekDays = new() { "måndag", "tisdag", "onsdag", "torsdag", "fredag" };
13+
14+
15+
public async Task<Result<RestaurantResult, Exception>> GetWeeklyLunchMenu(string url) =>
16+
await NavigateToPage(url)
17+
.Bind(wp => ExtractAllLinks(wp, url))
18+
.Bind(FindLunchPage)
19+
.Bind(GetMenusFromPage)
20+
.Map(x => new RestaurantResult
21+
{
22+
LunchMenus = x,
23+
RestaurantUrl = url
24+
});
25+
26+
private Result<List<LunchMenuResult>, Exception> GetMenusFromPage(string pageContent) =>
27+
ContainsAllWeekDays(pageContent, SwedishWeekDays)
28+
? GetLunchesFromPage(pageContent, SwedishWeekDays)
29+
: new Exception("Could not find all weekdays");
30+
31+
private Result<List<LunchMenuResult>, Exception> GetLunchesFromPage(string pageContent,
32+
List<string> swedishWeekDays) =>
33+
Result.Try<List<LunchMenuResult>, Exception>(() =>
34+
{
35+
var results = new List<LunchMenuResult>();
36+
foreach (var weekDay in swedishWeekDays)
37+
{
38+
var nextWeekDay = weekDay == "fredag" ? null : swedishWeekDays[swedishWeekDays.IndexOf(weekDay) + 1];
39+
int indexOfWeekDay = pageContent.IndexOf(weekDay, StringComparison.InvariantCultureIgnoreCase);
40+
if (nextWeekDay is not null)
41+
{
42+
var lunchMenuDataResult = Result.Try(() =>
43+
{
44+
var indexOfNextWeekDay =
45+
pageContent.IndexOf(nextWeekDay, StringComparison.InvariantCultureIgnoreCase);
46+
return pageContent.Substring(indexOfWeekDay, indexOfNextWeekDay - indexOfWeekDay);
47+
});
48+
49+
var lunchMenuResult = GetLunchMenuResult(lunchMenuDataResult, weekDay);
50+
51+
if (lunchMenuResult.IsSuccess)
52+
results.Add(lunchMenuResult.Value);
53+
}
54+
else
55+
{
56+
const int takeNoOfRows = 10;
57+
var lunchMenuDataResult = Result.Try(() =>
58+
{
59+
var indexOfLastWeekDay = pageContent.IndexOf(weekDay, StringComparison.InvariantCultureIgnoreCase);
60+
var rowsAfterFriday = pageContent.Substring(indexOfLastWeekDay).Split("\n").Take(takeNoOfRows);
61+
return string.Join("\n", rowsAfterFriday);
62+
});
63+
64+
var lunchMenuResult = GetLunchMenuResult(lunchMenuDataResult, weekDay);
65+
if (lunchMenuResult.IsSuccess)
66+
results.Add(lunchMenuResult.Value);
67+
}
68+
}
69+
70+
return results;
71+
}, e => e);
72+
73+
private Result<LunchMenuResult> GetLunchMenuResult(Result<string> lunchMenuDataResult, string weekDay) =>
74+
lunchMenuDataResult
75+
.Ensure(s => !string.IsNullOrEmpty(s), "No lunch menu found")
76+
.Ensure(s => s.Contains(weekDay, StringComparison.InvariantCultureIgnoreCase), "Not weekly menu")
77+
.MapTry(s => RemoveWeekDay(s, weekDay))
78+
.MapTry(WebUtility.HtmlDecode)
79+
.MapTry(s => s.Trim())
80+
.MapTry(s => s.TrimStart('\n'))
81+
.MapTry(s => s.Replace("\n", "<br>"))
82+
.MapTry(RemoveConsecutiveSpaces)
83+
.Map(s => new LunchMenuResult { Menu = s, Weekday = weekDay });
84+
85+
private string RemoveConsecutiveSpaces(string lunchMenu)
86+
{
87+
lunchMenu = Regex.Replace(lunchMenu, @"\s+", " ");
88+
return lunchMenu;
89+
}
90+
91+
private string RemoveWeekDay(string lunchMenu, string weekDay)
92+
{
93+
var lastIndexOfWeekDay = lunchMenu.LastIndexOf(weekDay, StringComparison.InvariantCultureIgnoreCase);
94+
lunchMenu = lunchMenu[(lastIndexOfWeekDay+weekDay.Length)..];
95+
//lunchMenu = lunchMenu.Replace(weekDay, "", StringComparison.InvariantCultureIgnoreCase);
96+
return lunchMenu;
97+
}
98+
99+
private static bool ContainsAllWeekDays(string pageContent, List<string> swedishWeekDays) =>
100+
swedishWeekDays.All(x => pageContent.ToLower().Contains(x));
101+
102+
private async Task<Result<string, Exception>> FindLunchPage(IEnumerable<Uri> links)
103+
{
104+
try
105+
{
106+
var lunchPageLink = links.FirstOrDefault(x => x.AbsoluteUri.ToLower().Contains("lunch"));
107+
if (lunchPageLink == null)
108+
{
109+
return new Exception("Could not find any lunch page");
110+
}
111+
112+
return await Result.Try<string, Exception>(
113+
async () => await GetLunchPageText(lunchPageLink),
114+
e => e);
115+
}
116+
catch (Exception e)
117+
{
118+
return e;
119+
}
120+
}
121+
122+
private async Task<string> GetLunchPageText(Uri lunchPageLink)
123+
{
124+
var webpage = await _browser.NavigateToPageAsync(lunchPageLink);
125+
var lunchPageText = webpage.Html.InnerText;
126+
lunchPageText = lunchPageText.Replace("\r", "");
127+
lunchPageText = lunchPageText.Replace("\t", "");
128+
return lunchPageText;
129+
}
130+
131+
private Result<IEnumerable<Uri>, Exception> ExtractAllLinks(WebPage webpage, string url) =>
132+
Result.Try<IEnumerable<Uri>, Exception>(() =>
133+
{
134+
var links = webpage.Html.CssSelect("a");
135+
var lunchLinks = links.Where(x => x.InnerText.ToLower().Contains("lunch"))
136+
.Select(x => new Uri(new Uri(url), x.Attributes["href"].Value))
137+
.ToList();
138+
return lunchLinks;
139+
140+
}, e => e);
141+
142+
private async Task<Result<WebPage, Exception>> NavigateToPage(string url)
143+
{
144+
try
145+
{
146+
return await _browser.NavigateToPageAsync(new Uri(url));
147+
}
148+
catch (Exception e)
149+
{
150+
return e;
151+
}
152+
}
153+
}
154+
155+
public interface ILogger<T>
156+
{
157+
Task Log(string s);
158+
}
159+
160+
public interface IRestaurantCrawler
161+
{
162+
}
163+
164+
public class RestaurantResult
165+
{
166+
public List<LunchMenuResult> LunchMenus { get; set; }
167+
public string RestaurantUrl { get; set; }
168+
}
169+
170+
public class LunchMenuResult
171+
{
172+
public string Menu { get; set; }
173+
public string Weekday { get; set; }
174+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net7.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.7"/>
11+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0"/>
12+
</ItemGroup>
13+
14+
</Project>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
var builder = WebApplication.CreateBuilder(args);
2+
3+
// Add services to the container.
4+
5+
builder.Services.AddControllers();
6+
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
7+
builder.Services.AddEndpointsApiExplorer();
8+
builder.Services.AddSwaggerGen();
9+
10+
var app = builder.Build();
11+
12+
// Configure the HTTP request pipeline.
13+
if (app.Environment.IsDevelopment())
14+
{
15+
app.UseSwagger();
16+
app.UseSwaggerUI();
17+
}
18+
19+
app.UseHttpsRedirection();
20+
21+
app.UseAuthorization();
22+
23+
app.MapControllers();
24+
25+
app.Run();
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"$schema": "https://json.schemastore.org/launchsettings.json",
3+
"iisSettings": {
4+
"windowsAuthentication": false,
5+
"anonymousAuthentication": true,
6+
"iisExpress": {
7+
"applicationUrl": "http://localhost:19023",
8+
"sslPort": 44395
9+
}
10+
},
11+
"profiles": {
12+
"http": {
13+
"commandName": "Project",
14+
"dotnetRunMessages": true,
15+
"launchBrowser": true,
16+
"launchUrl": "swagger",
17+
"applicationUrl": "http://localhost:5293",
18+
"environmentVariables": {
19+
"ASPNETCORE_ENVIRONMENT": "Development"
20+
}
21+
},
22+
"https": {
23+
"commandName": "Project",
24+
"dotnetRunMessages": true,
25+
"launchBrowser": true,
26+
"launchUrl": "swagger",
27+
"applicationUrl": "https://localhost:7016;http://localhost:5293",
28+
"environmentVariables": {
29+
"ASPNETCORE_ENVIRONMENT": "Development"
30+
}
31+
},
32+
"IIS Express": {
33+
"commandName": "IISExpress",
34+
"launchBrowser": true,
35+
"launchUrl": "swagger",
36+
"environmentVariables": {
37+
"ASPNETCORE_ENVIRONMENT": "Development"
38+
}
39+
}
40+
}
41+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace LunchTodayApi.RestaurantFinders;
2+
3+
public class IRestaurantFinder
4+
{
5+
6+
}

0 commit comments

Comments
 (0)