Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Selfhosting #8

Merged
merged 49 commits into from
Nov 4, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
3e894c7
create local db if not exists
3036662 Oct 26, 2023
9877c13
choose mySql if self_hosted
3036662 Oct 26, 2023
98c58be
include nuget Pamelo Mysql module dependencie
3036662 Oct 26, 2023
dab91b3
return message if now OpenAi token exists
3036662 Oct 26, 2023
baee4d9
use SMTPMailFrom variable if self-hosted
3036662 Oct 26, 2023
fd98596
add httpGet method to show change-password page
3036662 Oct 26, 2023
7ddd41c
added a class UserLocalStorageManager
3036662 Oct 26, 2023
91f8842
dependency injection for local storages
3036662 Oct 26, 2023
456907e
changed a method of parsing string to double
3036662 Oct 26, 2023
05d5239
fix constructors for localStorage managers
3036662 Oct 26, 2023
1b2514e
fix emailSender reset link
3036662 Oct 26, 2023
0d336ae
add reset password methods for self-hosted
3036662 Oct 26, 2023
9845810
fix accidentally doubled injection
3036662 Oct 26, 2023
5a09c73
fix a typo
3036662 Oct 30, 2023
b6a79fb
move dataDir to constructor local variable
3036662 Oct 30, 2023
8108de6
overwrite book cover file if exists
3036662 Oct 30, 2023
8682438
fix a typo
3036662 Oct 30, 2023
15d8659
move dataDir and baseDir to constructor local variables
3036662 Oct 30, 2023
4b46cee
move baseDir and dataDir to constructor local variables
3036662 Oct 30, 2023
4f93c07
overwrite profilePicture if exists
3036662 Oct 30, 2023
684354c
Throw exception if no openAi token exists
3036662 Oct 30, 2023
b3e923c
no return needed after throwing exception
3036662 Oct 30, 2023
c5da3bc
add accidantly deleted filename variable
3036662 Oct 30, 2023
c317980
add accidantly deleted filename variable
3036662 Oct 30, 2023
5ba9469
fix file attributes check when trying to overwrite
3036662 Oct 30, 2023
7438615
fix unreachable code in overwrite error handling
3036662 Oct 30, 2023
3701aa5
log to console when throwing exception (error overwriting file)
3036662 Oct 30, 2023
40516f3
Fix formatting and comments
DavidLazarescu Nov 1, 2023
e0943d1
add install and run instructions for self-hosted linux server
3036662 Nov 1, 2023
ddb270d
del dublicates in apt install command
3036662 Nov 2, 2023
03d355a
fix install.md filename to self-hosted-install.md
3036662 Nov 2, 2023
761683f
add ubuntu install-and-run instruction
3036662 Nov 2, 2023
0b849cc
fixed .md file name for ubuntu
3036662 Nov 2, 2023
a72b8b1
fixed path for configuring kesrel
3036662 Nov 2, 2023
78b4d3a
Merge branch 'Librum-Reader:main' into PR_techSelection
3036662 Nov 2, 2023
13dbd80
fixed a typo
3036662 Nov 2, 2023
be3388c
add sudo to chown command
3036662 Nov 2, 2023
41faf3f
replace apt-get with apt
3036662 Nov 2, 2023
dbd9ef2
Fixed updating highlights
DavidLazarescu Nov 4, 2023
9e683c2
Fixed librum-server.7
DavidLazarescu Nov 4, 2023
09cb68d
Improved language in librum-server.conf
DavidLazarescu Nov 4, 2023
01a1473
Update librum-server.service
DavidLazarescu Nov 4, 2023
bd87485
Delete self-hosting/self-hosted-install.md
DavidLazarescu Nov 4, 2023
5c08229
Improved the installation guide
DavidLazarescu Nov 4, 2023
a5254fb
Fixed dotnet installation on ubuntu
DavidLazarescu Nov 4, 2023
db8eb67
Added missing apt update after adding PPA
DavidLazarescu Nov 4, 2023
32b9e08
Link to dotnet installation page due to problems with the installatio…
DavidLazarescu Nov 4, 2023
52fc183
Added missing groupadd instruction
DavidLazarescu Nov 4, 2023
d06ae7f
Removed unnecessary instruction
DavidLazarescu Nov 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions src/Application/Managers/BookLocalStorageManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using Application.Common.Exceptions;
using Application.Interfaces.Managers;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;

namespace Application.Managers;

public class BookLocalStorageManager : IBookBlobStorageManager
{
private readonly string _bookCoverPrefix = "cover_";
private string dataDir;
3036662 marked this conversation as resolved.
Show resolved Hide resolved
private string booksDir;
private string coversDir;
private string baseDir;

public BookLocalStorageManager()
{
baseDir=System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
dataDir= baseDir+"/librum_srorage";
3036662 marked this conversation as resolved.
Show resolved Hide resolved
booksDir= dataDir + "/books";
coversDir=booksDir + "/covers";
// create folders
if(! System.IO.Directory.Exists(dataDir)) System.IO.Directory.CreateDirectory(dataDir);
if(! System.IO.Directory.Exists(booksDir)) System.IO.Directory.CreateDirectory(booksDir);
if(! System.IO.Directory.Exists(coversDir)) System.IO.Directory.CreateDirectory(coversDir);
Console.WriteLine ("Current directory is "+ dataDir);
}

public Task<Stream> DownloadBookBlob(Guid guid)
{
var filename=booksDir+"/"+guid;
if (!System.IO.File.Exists(filename)){
throw new CommonErrorException(400,"file not exists "+filename ,0);
}
return Task.FromResult<Stream>(File.OpenRead(filename));
}

public async Task UploadBookBlob(Guid guid, MultipartReader reader)
{
//if already exists
var filename=booksDir+"/"+guid;
if ( System.IO.File.Exists(filename)){
throw new CommonErrorException(400,"file already exists "+filename ,0);
}
System.IO.Stream dest;
try {
dest = System.IO.File.Create (filename);
}
catch (Exception e)
{
throw new CommonErrorException(400, "Can't create file", 0);
}


var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition,
out var contentDisposition);

if (!hasContentDispositionHeader)
continue;

if (!HasFileContentDisposition(contentDisposition))
{
var message = "Missing content disposition header";
throw new CommonErrorException(400, message, 0);
}

await section.Body.CopyToAsync(dest);

section = await reader.ReadNextSectionAsync();
}
dest.Dispose();
System.IO.File.SetUnixFileMode(filename,UnixFileMode.UserRead | UnixFileMode.UserWrite);
}

private static bool HasFileContentDisposition(
ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null &&
contentDisposition.DispositionType.Equals("form-data") &&
(!string.IsNullOrEmpty(contentDisposition.FileName.Value) ||
!string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
}


public async Task DeleteBookBlob(Guid guid)
{
var path=booksDir+"/"+guid;
await Task.Run(() => System.IO.File.Delete(path));
}

public async Task<long> ChangeBookCover(Guid guid, MultipartReader reader)
{
//if already exists
var filename=coversDir+"/"+guid;
if ( System.IO.File.Exists(filename)){
3036662 marked this conversation as resolved.
Show resolved Hide resolved
throw new CommonErrorException(400,"file already exists "+filename ,0);
}
System.IO.Stream dest;
try {
dest = System.IO.File.Create (filename);
}
catch (Exception e)
{
throw new CommonErrorException(400, "Can't create file for book cover", 0);
}

long coverSize = 0;
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition,
out var contentDisposition);

if (!hasContentDispositionHeader)
continue;

if (!HasFileContentDisposition(contentDisposition))
{
var message = "Missing content disposition header";
throw new CommonErrorException(400, message, 0);
}

await section.Body.CopyToAsync(dest);
coverSize += section.Body.Length;

section = await reader.ReadNextSectionAsync();
}
dest.Dispose();
System.IO.File.SetUnixFileMode(filename,UnixFileMode.UserRead | UnixFileMode.UserWrite);

return coverSize;
}

public Task<Stream> DownloadBookCover(Guid guid)
{
var filename=coversDir+"/"+guid;
if (!System.IO.File.Exists(filename)){
throw new CommonErrorException(400,"file not exists "+filename ,0);
}
return Task.FromResult<Stream>(File.OpenRead(filename));
}

public async Task DeleteBookCover(Guid guid)
{
var path=coversDir+"/"+guid;
await Task.Run(() => System.IO.File.Delete(path));
}
}
92 changes: 92 additions & 0 deletions src/Application/Managers/UserLocalStorageManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using Application.Common.Exceptions;
using Application.Interfaces.Managers;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;

namespace Application.Managers;

public class UserLocalStorageManager : IUserBlobStorageManager
{
private readonly string _profilePicturePrefix = "profilePicture_";
private string dataDir;
3036662 marked this conversation as resolved.
Show resolved Hide resolved
private string profilesDir;
private string baseDir;

public UserLocalStorageManager()
{
baseDir=System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal); // $HOME folder
dataDir= baseDir+"/librum_srorage";
3036662 marked this conversation as resolved.
Show resolved Hide resolved
profilesDir=dataDir+"/profiles";
// create folders
if(! System.IO.Directory.Exists(dataDir)) System.IO.Directory.CreateDirectory(dataDir);
if(! System.IO.Directory.Exists(profilesDir)) System.IO.Directory.CreateDirectory(profilesDir);
Console.WriteLine ("Profile photos directory is "+ profilesDir);
}


public Task<Stream> DownloadProfilePicture(string guid)
{
var filename=profilesDir+"/"+guid;
if (!System.IO.File.Exists(filename)){
throw new CommonErrorException(400,"file not exists "+filename ,0);
}
return Task.FromResult<Stream>(File.OpenRead(filename));
}

public async Task ChangeProfilePicture(string guid, MultipartReader reader)
{
//if already exists
var filename=profilesDir+"/"+guid;
3036662 marked this conversation as resolved.
Show resolved Hide resolved
if ( System.IO.File.Exists(filename)){
throw new CommonErrorException(400,"file already exists "+filename ,0);
}
System.IO.Stream dest;
try {
dest = System.IO.File.Create (filename);
}
catch (Exception e)
{
throw new CommonErrorException(400, "Can't create file "+filename, 0);
Console.WriteLine(e.Message);
}

var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition,
out var contentDisposition);

if (!hasContentDispositionHeader)
continue;

if (!HasFileContentDisposition(contentDisposition))
{
var message = "Missing content disposition header";
throw new CommonErrorException(400, message, 0);
}

await section.Body.CopyToAsync(dest);
section = await reader.ReadNextSectionAsync();
}
dest.Dispose();
System.IO.File.SetUnixFileMode(filename,UnixFileMode.UserRead | UnixFileMode.UserWrite);
}

private static bool HasFileContentDisposition(
ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null &&
contentDisposition.DispositionType.Equals("form-data") &&
(!string.IsNullOrEmpty(contentDisposition.FileName.Value) ||
!string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
}

public async Task DeleteProfilePicture(string guid)
{
var path=profilesDir+"/"+guid;
await Task.Run(() => System.IO.File.Delete(path));
}
}
6 changes: 6 additions & 0 deletions src/Application/Services/AIService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public AiService(ILogger<AiService> logger, IHttpClientFactory httpClientFactory
public async Task ExplainAsync(string email, HttpContext context, string text,
string mode)
{
// return error mesage if no OpenAIToken exists for self-hosted mode, dont throw exception
if (_configuration["LIBRUM_SELFHOSTED"] == "true" && String.IsNullOrEmpty(_configuration["OpenAIToken"])){
await context.Response.WriteAsync("data: " + "OpenAI temporary unavailable" + "\n");
3036662 marked this conversation as resolved.
Show resolved Hide resolved
return;
}

var user = await _userRepository.GetAsync(email, trackChanges: true);
if(user.AiExplanationRequestsMadeToday >= 10)
{
Expand Down
29 changes: 25 additions & 4 deletions src/Application/Utility/EmailSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ public async Task SendEmailConfirmationEmail(User user, string token)
var confirmationLink = GetEmailConfirmationLink(user, token);

var message = new MimeMessage();
message.From.Add (new MailboxAddress ("Librum", "[email protected]"));
// message from Librum if not self-hosted
if (_configuration["LIBRUM_SELFHOSTED"] != "true"){
message.From.Add (new MailboxAddress ("Librum", "[email protected]"));
}
else{
var messFrom = _configuration["SMTPMailFrom"];
message.From.Add (new MailboxAddress ("Librum", messFrom));
}
message.To.Add (new MailboxAddress (user.FirstName, user.Email));
message.Subject = "Confirm Your Email";

Expand All @@ -47,10 +54,24 @@ public async Task SendEmailConfirmationEmail(User user, string token)

public async Task SendPasswordResetEmail(User user, string token)
{
var resetLink = $"https://librumreader.com/resetPassword?email={user.Email}&token={token}";

// go to librum site if not selfhosted
var resetLink = $"https://librumreader.com/resetPassword?email={user.Email}&token={token}";
// if self hosted change resetlink
if (_configuration["LIBRUM_SELFHOSTED"] == "true"){
var domain =_configuration["CleanUrl"];
var encodedToken=System.Web.HttpUtility.HtmlEncode(token);
resetLink = $"{domain}/user/resetPassword?email={user.Email}&token={encodedToken}";
3036662 marked this conversation as resolved.
Show resolved Hide resolved
}

var message = new MimeMessage();
message.From.Add (new MailboxAddress ("Librum", "[email protected]"));
// message from librum if not self-hosted
if (_configuration["LIBRUM_SELFHOSTED"] != "true"){
message.From.Add (new MailboxAddress ("Librum", "[email protected]"));
}
else{
var messFrom = _configuration["SMTPMailFrom"];
message.From.Add (new MailboxAddress ("Librum",messFrom));
}
message.To.Add (new MailboxAddress (user.FirstName, user.Email));
message.Subject = "Reset Your Password";

Expand Down
11 changes: 10 additions & 1 deletion src/Infrastructure/Persistence/Repository/BookRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,16 @@ private double GetBytesFromSizeString(string size)
}

var numberString = size.Substring(0, typeBegining);
var numbers = double.Parse(numberString);
// ------------
// double.Parse was throwing exception on linux so
//var numbers = double.Parse(numberString,number);
System.Globalization.NumberFormatInfo provider = new System.Globalization.NumberFormatInfo();
provider.NumberDecimalSeparator = ".";
provider.NumberGroupSeparator = ",";
var numbers = Convert.ToDouble(numberString,provider);
// end of changes
// --------------

var type = size[typeBegining..];

return type.ToLower() switch
Expand Down
Loading