feat: add database connections, initial migration and early CLI tooling
All checks were successful
Update changelog / changelog (push) Successful in 25s
All checks were successful
Update changelog / changelog (push) Successful in 25s
This commit is contained in:
304
Controllers/Cli.cs
Normal file
304
Controllers/Cli.cs
Normal file
@@ -0,0 +1,304 @@
|
||||
using Shadow.Data;
|
||||
using Shadow.Entities;
|
||||
using System.Security;
|
||||
|
||||
namespace Shadow.Controllers;
|
||||
public class Cli
|
||||
{
|
||||
private readonly ApplicationDbContext db;
|
||||
private readonly GeneralUseHelpers guhf;
|
||||
private readonly string[] args;
|
||||
|
||||
// TODO: Add "changeUser"
|
||||
|
||||
public Cli(ApplicationDbContext _db, GeneralUseHelpers _guhf, string[] _args)
|
||||
{
|
||||
db = _db;
|
||||
guhf = _guhf;
|
||||
args = _args;
|
||||
}
|
||||
|
||||
public async Task<bool> Parse()
|
||||
{
|
||||
// Returns true if the program can finish execution.
|
||||
bool exit = true;
|
||||
// Check if anything has been passed
|
||||
if (args.Length == 0 || args.FirstOrDefault() is null) {
|
||||
return true;
|
||||
}
|
||||
switch (args[0].ToLower())
|
||||
{
|
||||
case "adduser":
|
||||
AddUser();
|
||||
break;
|
||||
case "resetpassword":
|
||||
ResetPassword();
|
||||
break;
|
||||
case "removeuser":
|
||||
RemoveUser();
|
||||
break;
|
||||
case "help":
|
||||
case "--help":
|
||||
case "h":
|
||||
case "-h":
|
||||
case "/h":
|
||||
case "/?":
|
||||
case "?":
|
||||
ShowHelp();
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine($"Unknown option: \"{args[0]}\". See \"help\" for available arguments.");
|
||||
break;
|
||||
}
|
||||
|
||||
return exit;
|
||||
}
|
||||
|
||||
public void ShowHelp()
|
||||
{
|
||||
// Shown when "Shadow --help"/"Shadow help"/... is ran.
|
||||
Console.WriteLine(
|
||||
$"--- Shadow commandline utility ---\n" +
|
||||
$"Shadow version: #{ThisAssembly.Git.Commit} {ThisAssembly.Git.Branch} ({ThisAssembly.Git.CommitDate})\n\n" +
|
||||
$"Available commands:\n" +
|
||||
$"- addUser [username] - create a new user,\n" +
|
||||
$"- resetPassword [username] - reset a user's password,\n" +
|
||||
$"- removeUser [username] - remove the user COMPLETELY.\n\n" +
|
||||
$"Username is optional. If not provided, user will be prompted to enter it.\n" +
|
||||
$"Running without specifying a command launches a Kestrel web server.\n\n" +
|
||||
$"License: AGPLv3+, Source code: https://gitea.7o7.cx/sherl/Shadow"
|
||||
);
|
||||
}
|
||||
|
||||
public void AddUser()
|
||||
{
|
||||
// Check if any cli arguments have been passed
|
||||
// Both:
|
||||
// Shadow addUser
|
||||
// and
|
||||
// Shadow addUser username
|
||||
// are supported.
|
||||
|
||||
Console.WriteLine($"[Shadow] Add a new user");
|
||||
if (args.Length == 2)
|
||||
Console.WriteLine($" You will be promped to enter a password.");
|
||||
else
|
||||
Console.WriteLine($" You will be promped to enter a username and password.");
|
||||
|
||||
string? username = null;
|
||||
if (args.Length != 2)
|
||||
{
|
||||
while (username is null || username == String.Empty)
|
||||
username = ReadName(" Please enter a username: ");
|
||||
}
|
||||
|
||||
// Check if user by this name exists in DB
|
||||
User? foundUser = db.Users
|
||||
.FirstOrDefault(u => u.NormalizedName == username!.ToLower());
|
||||
if (foundUser != null) {
|
||||
Console.WriteLine("Error! User with this name already exists in the database!");
|
||||
return;
|
||||
}
|
||||
|
||||
string password = ReadPassword();
|
||||
string passwordConfirmation = ReadPassword(" Confirm password: ");
|
||||
if (!password.Equals(passwordConfirmation))
|
||||
{
|
||||
Console.WriteLine("Error! Passwords do not match. Please try again.");
|
||||
return;
|
||||
}
|
||||
bool isUserAdmin = YesNoPrompt($" Should \"{username}\" be an administrator? [y/N]: ", false);
|
||||
|
||||
User newUser = new User
|
||||
{
|
||||
Name = username!,
|
||||
NormalizedName = username!.ToLower(),
|
||||
Password = password
|
||||
};
|
||||
|
||||
db.Users.Add(newUser);
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
public void ResetPassword()
|
||||
{
|
||||
// Check if any cli arguments have been passed
|
||||
// Both:
|
||||
// Shadow resetPassword
|
||||
// and
|
||||
// Shadow resetPassword username
|
||||
// are supported.
|
||||
|
||||
Console.WriteLine($"[Shadow] Reset password");
|
||||
if (args.Length == 2)
|
||||
Console.WriteLine($" You will be promped to enter a new password.");
|
||||
else
|
||||
Console.WriteLine($" You will be promped to enter a username and a new password.");
|
||||
|
||||
string? username = null;
|
||||
if (args.Length != 2)
|
||||
{
|
||||
while (username is null || username == String.Empty)
|
||||
username = ReadName(" Please enter a username: ");
|
||||
}
|
||||
|
||||
// Check if user by this name exists in DB
|
||||
User? foundUser = db.Users
|
||||
.FirstOrDefault(u => u.NormalizedName == username!.ToLower());
|
||||
if (foundUser == null)
|
||||
{
|
||||
Console.WriteLine("Error! User with this name does not exist in the database!");
|
||||
return;
|
||||
}
|
||||
|
||||
string password = ReadPassword();
|
||||
string passwordConfirmation = ReadPassword(" Confirm new password: ");
|
||||
if (!password.Equals(passwordConfirmation))
|
||||
{
|
||||
Console.WriteLine("Error! Passwords do not match. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
foundUser.Password = password;
|
||||
db.SaveChanges();
|
||||
|
||||
}
|
||||
|
||||
public void RemoveUser()
|
||||
{
|
||||
// Check if any cli arguments have been passed
|
||||
// Both:
|
||||
// Shadow removeUser
|
||||
// and
|
||||
// Shadow removeUser username
|
||||
// are supported.
|
||||
|
||||
Console.WriteLine($"[Shadow] Remove user");
|
||||
if (args.Length == 2)
|
||||
Console.WriteLine($" You will be promped to enter the password.");
|
||||
else
|
||||
Console.WriteLine($" You will be promped to enter the username and password.");
|
||||
|
||||
string? username = null;
|
||||
if (args.Length != 2)
|
||||
{
|
||||
while (username is null || username == String.Empty)
|
||||
username = ReadName(" Please enter the username: ");
|
||||
}
|
||||
|
||||
// Check if user by this name exists in DB
|
||||
User? foundUser = db.Users
|
||||
.FirstOrDefault(u => u.NormalizedName == username!.ToLower());
|
||||
if (foundUser == null)
|
||||
{
|
||||
Console.WriteLine("Error! User with this name does not exist in the database!");
|
||||
return;
|
||||
}
|
||||
|
||||
string password = ReadPassword();
|
||||
string passwordConfirmation = ReadPassword(" Confirm password: ");
|
||||
if (!password.Equals(passwordConfirmation))
|
||||
{
|
||||
Console.WriteLine("Error! Passwords do not match. Please try again.");
|
||||
return;
|
||||
}
|
||||
if (foundUser.Password != password)
|
||||
{
|
||||
Console.WriteLine($"Error! Entered password does not match that of \"{username}\"!");
|
||||
return;
|
||||
}
|
||||
|
||||
bool userDeletionConfirmation = YesNoPrompt($" Do you want to remove \"{username}\" completely?\n" +
|
||||
$" This cannot be undone! [y/N]: ", false);
|
||||
if (userDeletionConfirmation) {
|
||||
|
||||
// All playlists, interactions should be deleted as well.
|
||||
List<AlbumInteraction> albumInteractions = db.AlbumInteractions
|
||||
.Where(ai => ai.User == foundUser)
|
||||
.ToList();
|
||||
|
||||
List<SongInteraction> songInteractions = db.SongInteractions
|
||||
.Where(si => si.User == foundUser)
|
||||
.ToList();
|
||||
|
||||
foreach(AlbumInteraction ai in albumInteractions)
|
||||
db.AlbumInteractions.Remove(ai);
|
||||
|
||||
foreach (SongInteraction si in songInteractions)
|
||||
db.SongInteractions.Remove(si);
|
||||
|
||||
db.Users.Remove(foundUser);
|
||||
db.SaveChanges();
|
||||
|
||||
Console.WriteLine($"User \"{username}\" and all their data deleted successfully.");
|
||||
}
|
||||
else
|
||||
Console.WriteLine("User not removed.");
|
||||
|
||||
}
|
||||
|
||||
public string ReadPassword(string prompt = " Enter password (will not be echoed back): ")
|
||||
{
|
||||
// https://www.silicloud.com/blog/how-to-hide-content-in-the-console-using-c/
|
||||
string password = String.Empty;
|
||||
ConsoleKeyInfo key;
|
||||
|
||||
bool exit = false;
|
||||
Console.Write(prompt);
|
||||
while (!exit) {
|
||||
key = Console.ReadKey(true);
|
||||
|
||||
// Exit on enter
|
||||
if (key.Key == ConsoleKey.Enter)
|
||||
exit = true;
|
||||
// Clear last character on backspace
|
||||
else if (key.Key == ConsoleKey.Backspace && password.Length > 0)
|
||||
password = password.Substring(0, (password.Length - 1));
|
||||
// Append any other character
|
||||
else
|
||||
password += key.KeyChar;
|
||||
}
|
||||
Console.WriteLine();
|
||||
return password;
|
||||
}
|
||||
|
||||
public string? ReadName(string prompt = " Enter username: ")
|
||||
{
|
||||
Console.Write(prompt);
|
||||
string? input = Console.ReadLine();
|
||||
return input;
|
||||
}
|
||||
|
||||
public bool YesNoPrompt(string prompt, bool? default_value = null)
|
||||
{
|
||||
// Checks if user input starts with "y", and if it does, returns true.
|
||||
// Otherwise checks for "n". If both checks fail, and default_value is null,
|
||||
// user will be asked repeatedly.
|
||||
bool exit = false;
|
||||
bool response = false;
|
||||
|
||||
Console.Write(prompt);
|
||||
string? input = Console.ReadLine();
|
||||
|
||||
while (!exit)
|
||||
{
|
||||
if (input is not null && input.Length > 0)
|
||||
if (input.ToLower().StartsWith("y"))
|
||||
{
|
||||
response = true;
|
||||
exit = true;
|
||||
}
|
||||
else if (input.ToLower().StartsWith("n"))
|
||||
{
|
||||
response = false;
|
||||
exit = true;
|
||||
}
|
||||
else if (default_value is not null)
|
||||
{
|
||||
response = (bool)default_value;
|
||||
exit = true;
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user