Files
Shadow/Controllers/Cli.cs
sherl efb8a41065
All checks were successful
Update changelog / changelog (push) Successful in 26s
fix: use the default value on enter, read username from cli arguments
2025-12-11 15:39:40 +01:00

312 lines
8.1 KiB
C#

using Shadow.Data;
using Shadow.Entities;
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: ");
}
else
username = args[1];
// 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: ");
}
else
username = args[1];
// 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: ");
}
else
username = args[1];
// 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;
}
}