diff --git a/Presence.Api/Controllers/GroupController.cs b/Presence.Api/Controllers/GroupController.cs new file mode 100644 index 0000000..142f999 --- /dev/null +++ b/Presence.Api/Controllers/GroupController.cs @@ -0,0 +1,45 @@ +using domain.Service; +using domain.UseCase; +using domain.Request; +using domain.Service; +using Microsoft.AspNetCore.Mvc; +using Presence.Api.Response; + +namespace Presence.Api.Controllers +{ + [ApiController] + [Route("[controller]/api")] + public class GroupController: ControllerBase + { + private readonly IGroupUseCase _groupService; + + public GroupController(IGroupUseCase groupService) + { + _groupService = groupService; + } + + [HttpGet("/group")] + public async Task> GetAllGroups() + { + var result = _groupService + .GetGroupsWithStudents(); + var response = result + .Select(group => new GroupResponse { + Id = group.Id, + Name = group.Name, + Users = group.Users.Select(user => new UserResponse { + Guid = user.Guid, + Name = user.Name + }).ToList(), + }).ToList(); + return Ok(new GroupResponse()); + } + + [HttpPost("/admin/{groupId}/students")] + public void AddGroup(int groupId, [FromBody] AddGroupRequest addGroupRequest) + { + Console.WriteLine(groupId); + _groupService.AddGroup(addGroupRequest); + } + } +} diff --git a/Presence.Api/Controllers/WeatherForecastController.cs b/Presence.Api/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..4ad6679 --- /dev/null +++ b/Presence.Api/Controllers/WeatherForecastController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Presence.Api.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/Presence.Api/Extensions/ServiceCollectionsExtension.cs b/Presence.Api/Extensions/ServiceCollectionsExtension.cs new file mode 100644 index 0000000..4321298 --- /dev/null +++ b/Presence.Api/Extensions/ServiceCollectionsExtension.cs @@ -0,0 +1,20 @@ +using data; +using data.Repository; +using domain.Service; +using domain.UseCase; +using Presence.Api.Controllers; + +namespace Presence.Api.Extensions +{ + public static class ServiceCollectionsExtension + { + public static void AddCommomService(this IServiceCollection services) + { + services + .AddDbContext() + .AddScoped() + .AddScoped() + .AddScoped(); + } + } +} diff --git a/Presence.Api/Presence.Api.csproj b/Presence.Api/Presence.Api.csproj new file mode 100644 index 0000000..3c0d11b --- /dev/null +++ b/Presence.Api/Presence.Api.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + diff --git a/Presence.Api/Presence.Api.http b/Presence.Api/Presence.Api.http new file mode 100644 index 0000000..68c2819 --- /dev/null +++ b/Presence.Api/Presence.Api.http @@ -0,0 +1,6 @@ +@Presence.Api_HostAddress = http://localhost:5028 + +GET {{Presence.Api_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/Presence.Api/Program.cs b/Presence.Api/Program.cs new file mode 100644 index 0000000..d640dd2 --- /dev/null +++ b/Presence.Api/Program.cs @@ -0,0 +1,29 @@ +using Presence.Api.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddCommomService(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/Presence.Api/Properties/launchSettings.json b/Presence.Api/Properties/launchSettings.json new file mode 100644 index 0000000..10c6513 --- /dev/null +++ b/Presence.Api/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:32613", + "sslPort": 44302 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5028", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7098;http://localhost:5028", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Presence.Api/Response/GroupResponse.cs b/Presence.Api/Response/GroupResponse.cs new file mode 100644 index 0000000..c252f0c --- /dev/null +++ b/Presence.Api/Response/GroupResponse.cs @@ -0,0 +1,9 @@ +namespace Presence.Api.Response +{ + public class GroupResponse + { + public int Id { get; set; } + public string Name { get; set; } + public IEnumerable? Users { get; set; } = null; + } +} diff --git a/Presence.Api/Response/UserResponse.cs b/Presence.Api/Response/UserResponse.cs new file mode 100644 index 0000000..c4f1df2 --- /dev/null +++ b/Presence.Api/Response/UserResponse.cs @@ -0,0 +1,8 @@ +namespace Presence.Api.Response +{ + public class UserResponse + { + public Guid Guid { get; set; } + public string Name { get; set; } + } +} diff --git a/Presence.Api/WeatherForecast.cs b/Presence.Api/WeatherForecast.cs new file mode 100644 index 0000000..86ff197 --- /dev/null +++ b/Presence.Api/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace Presence.Api +{ + public class WeatherForecast + { + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} diff --git a/Presence.Api/appsettings.Development.json b/Presence.Api/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Presence.Api/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Presence.Api/appsettings.json b/Presence.Api/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/Presence.Api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Presence.Desktop/App.axaml b/Presence.Desktop/App.axaml new file mode 100644 index 0000000..54c9d29 --- /dev/null +++ b/Presence.Desktop/App.axaml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/Presence.Desktop/App.axaml.cs b/Presence.Desktop/App.axaml.cs new file mode 100644 index 0000000..023b7c1 --- /dev/null +++ b/Presence.Desktop/App.axaml.cs @@ -0,0 +1,37 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Microsoft.Extensions.DependencyInjection; +using Presence.Desktop.DI; +using Presence.Desktop.ViewModels; +using Presence.Desktop.Views; + +namespace Presence.Desktop +{ + public partial class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddCommonService(); + var services = serviceCollection.BuildServiceProvider(); + var mainViewModel = services.GetRequiredService(); + + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow() + { + DataContext = new MainWindowViewModel(services), + }; + } + + base.OnFrameworkInitializationCompleted(); + } + + } +} \ No newline at end of file diff --git a/Presence.Desktop/Assets/avalonia-logo.ico b/Presence.Desktop/Assets/avalonia-logo.ico new file mode 100644 index 0000000..da8d49f Binary files /dev/null and b/Presence.Desktop/Assets/avalonia-logo.ico differ diff --git a/Presence.Desktop/DI/ServiceCollectionExtensions.cs b/Presence.Desktop/DI/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..5fd078a --- /dev/null +++ b/Presence.Desktop/DI/ServiceCollectionExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using data; +using data.Repository; +using domain.Service; +using domain.UseCase; +using Microsoft.Extensions.DependencyInjection; +using Presence.Desktop.ViewModels; + +namespace Presence.Desktop.DI +{ + public static class ServiceCollectionExtensions + { + public static void AddCommonService(this IServiceCollection collection) + { + collection + .AddDbContext() + .AddSingleton() + .AddTransient() + .AddTransient(); + } + } +} diff --git a/Presence.Desktop/Models/GroupPresenter.cs b/Presence.Desktop/Models/GroupPresenter.cs new file mode 100644 index 0000000..eb8c3be --- /dev/null +++ b/Presence.Desktop/Models/GroupPresenter.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Presence.Desktop.Models +{ + public class GroupPresenter + { + public int Id { get; set; } + public string Name { get; set; } + public IEnumerable? users { get; set; } = null; + } +} \ No newline at end of file diff --git a/Presence.Desktop/Models/UserPresenter.cs b/Presence.Desktop/Models/UserPresenter.cs new file mode 100644 index 0000000..3a8b4a8 --- /dev/null +++ b/Presence.Desktop/Models/UserPresenter.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Presence.Desktop.Models +{ + public class UserPresenter + { + public Guid Guid { get; set; } + public string Name { get; set; } + public GroupPresenter Group { get; set; } + } +} \ No newline at end of file diff --git a/Presence.Desktop/Presence.Desktop.csproj b/Presence.Desktop/Presence.Desktop.csproj new file mode 100644 index 0000000..33feb38 --- /dev/null +++ b/Presence.Desktop/Presence.Desktop.csproj @@ -0,0 +1,33 @@ + + + WinExe + net8.0 + enable + true + app.manifest + true + + + + + + + + + + + + + + None + All + + + + + + + + + + diff --git a/Presence.Desktop/Program.cs b/Presence.Desktop/Program.cs new file mode 100644 index 0000000..9d4a474 --- /dev/null +++ b/Presence.Desktop/Program.cs @@ -0,0 +1,24 @@ +using Avalonia; +using Avalonia.ReactiveUI; +using System; + +namespace Presence.Desktop +{ + internal sealed class Program + { + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace() + .UseReactiveUI(); + } +} diff --git a/Presence.Desktop/ViewLocator.cs b/Presence.Desktop/ViewLocator.cs new file mode 100644 index 0000000..2da0eb6 --- /dev/null +++ b/Presence.Desktop/ViewLocator.cs @@ -0,0 +1,19 @@ +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using Presence.Desktop.ViewModels; +using System; +using Presence.Desktop.Views; +using ReactiveUI; + +namespace Presence.Desktop +{ + public class ViewLocator : IViewLocator + { + public IViewFor? ResolveView(T? viewModel, string? contract = null) => viewModel switch + { + GroupViewModel groupViewModel => new GroupView { DataContext = groupViewModel }, + PresenceViewModel presenceViewModel => new PresenceView { DataContext = presenceViewModel }, + _ => throw new ArgumentOutOfRangeException(nameof(viewModel)) + }; + } +} diff --git a/Presence.Desktop/ViewModels/GroupViewModel.cs b/Presence.Desktop/ViewModels/GroupViewModel.cs new file mode 100644 index 0000000..3522b81 --- /dev/null +++ b/Presence.Desktop/ViewModels/GroupViewModel.cs @@ -0,0 +1,100 @@ +using domain.UseCase; +using Presence.Desktop.Models; +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Presence.Desktop.ViewModels +{ + public class GroupViewModel : ViewModelBase, IRoutableViewModel + { + public ICommand OpenFileDialog { get; } + public Interaction SelectFileInteraction => _SelectFileInteraction; + public readonly Interaction _SelectFileInteraction; + private string? _selectedFile; + public string? SelectedFile + { + get => _selectedFile; + set => this.RaiseAndSetIfChanged(ref _selectedFile, value); + } + + private readonly List _groupPresentersDataSource = new List(); + private ObservableCollection _groups; + public ObservableCollection Groups => _groups; + + public GroupPresenter? SelectedGroupItem + { + get => _selectedGroupItem; + set => this.RaiseAndSetIfChanged(ref _selectedGroupItem, value); + } + + private GroupPresenter? _selectedGroupItem; + + + private IGroupUseCase _groupUseCase; + public ObservableCollection Users { get => _users; } + public ObservableCollection _users; + public GroupViewModel(IGroupUseCase groupUseCase) + { + _groupUseCase = groupUseCase; + _SelectFileInteraction = new Interaction(); + OpenFileDialog = ReactiveCommand.CreateFromTask(SelectFile); + _users = new ObservableCollection(); + RefreshGroups(); + this.WhenAnyValue(vm => vm.SelectedGroupItem) + .Subscribe(_ => + { + RefreshGroups(); + SetUsers(); + }); + } + + private void SetUsers() + { + if (SelectedGroupItem == null) return; + Users.Clear(); + var group = _groups.First(it => it.Id == SelectedGroupItem.Id); + if (group.users == null) return; + foreach (var item in group.users) + { + Users.Add(item); + } + } + + private void RefreshGroups() + { + _groupPresentersDataSource.Clear(); + foreach (var item in _groupUseCase.GetGroupsWithStudents()) + { + GroupPresenter groupPresenter = new GroupPresenter + { + Id = item.Id, + Name = item.Name, + users = item.Users?.Select(user => new UserPresenter + { + Name = user.Name, + Guid = user.Guid, + Group = new GroupPresenter { Id = item.Id, Name = item.Name } + } + ).ToList() + }; + _groupPresentersDataSource.Add(groupPresenter); + } + _groups = new ObservableCollection(_groupPresentersDataSource); + } + + private async Task SelectFile() + { + Console.WriteLine("clock"); + SelectedFile = await _SelectFileInteraction.Handle("Chose csv file"); + } + public string? UrlPathSegment { get; } + public IScreen HostScreen { get; } + } +} + diff --git a/Presence.Desktop/ViewModels/MainWindowViewModel.cs b/Presence.Desktop/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..8449f0d --- /dev/null +++ b/Presence.Desktop/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,16 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using ReactiveUI; + +namespace Presence.Desktop.ViewModels; + +public class MainWindowViewModel : ViewModelBase, IScreen +{ + public RoutingState Router { get; } = new RoutingState(); + + public MainWindowViewModel(IServiceProvider serviceProvider) + { + var groupViewModel = serviceProvider.GetRequiredService(); + Router.Navigate.Execute(groupViewModel); + } +} \ No newline at end of file diff --git a/Presence.Desktop/ViewModels/PresenceViewModel.cs b/Presence.Desktop/ViewModels/PresenceViewModel.cs new file mode 100644 index 0000000..8ff3428 --- /dev/null +++ b/Presence.Desktop/ViewModels/PresenceViewModel.cs @@ -0,0 +1,9 @@ +using ReactiveUI; + +namespace Presence.Desktop.ViewModels; + +public class PresenceViewModel : ViewModelBase, IRoutableViewModel +{ + public string? UrlPathSegment { get; } + public IScreen HostScreen { get; } +} \ No newline at end of file diff --git a/Presence.Desktop/ViewModels/ViewModelBase.cs b/Presence.Desktop/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..66c82c5 --- /dev/null +++ b/Presence.Desktop/ViewModels/ViewModelBase.cs @@ -0,0 +1,8 @@ +using ReactiveUI; + +namespace Presence.Desktop.ViewModels +{ + public class ViewModelBase : ReactiveObject + { + } +} diff --git a/Presence.Desktop/Views/GroupView.axaml b/Presence.Desktop/Views/GroupView.axaml new file mode 100644 index 0000000..d1aed66 --- /dev/null +++ b/Presence.Desktop/Views/GroupView.axaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + +