commit 0b829425690848157a09773926a5d27206f8dbe4 Author: KoCoder Date: Thu Feb 12 19:35:17 2026 +0100 Initial Commit: BlackBoard inkl. Reporting diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fec345e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +bin/ +debug/ diff --git a/Client/AssemblyInfo.cs b/Client/AssemblyInfo.cs new file mode 100644 index 0000000..f97b79f --- /dev/null +++ b/Client/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Resources; +using Microsoft.Extensions.Localization; + +[assembly: RootNamespace("SZUAbsolventenverein.Module.BlackBoard.Client")] diff --git a/Client/Interop.cs b/Client/Interop.cs new file mode 100644 index 0000000..04562f8 --- /dev/null +++ b/Client/Interop.cs @@ -0,0 +1,15 @@ +using Microsoft.JSInterop; +using System.Threading.Tasks; + +namespace SZUAbsolventenverein.Module.BlackBoard +{ + public class Interop + { + private readonly IJSRuntime _jsRuntime; + + public Interop(IJSRuntime jsRuntime) + { + _jsRuntime = jsRuntime; + } + } +} diff --git a/Client/Modules/SZUAbsolventenverein.Module.BlackBoard/Edit.razor b/Client/Modules/SZUAbsolventenverein.Module.BlackBoard/Edit.razor new file mode 100644 index 0000000..d59f49f --- /dev/null +++ b/Client/Modules/SZUAbsolventenverein.Module.BlackBoard/Edit.razor @@ -0,0 +1,130 @@ +@using Interfaces +@using Oqtane.Modules.Controls +@using SZUAbsolventenverein.Module.BlackBoard.Services +@using SZUAbsolventenverein.Module.BlackBoard.Models + +@namespace SZUAbsolventenverein.Module.BlackBoard +@inherits ModuleBase +@inject IBlackBoardService BlackBoardService +@inject NavigationManager NavigationManager +@inject IReportingHandler ReportingHandler +@inject IStringLocalizer Localizer + +
+
+
+ +
+ +
+
+
+ + + @Localizer["Cancel"] +

+ @if (PageState.Action == "Edit") + { + + } +
+ +@code { + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; + + public override string Actions => "Add,Edit"; + + public override string Title => "Manage BlackBoard"; + + public override List Resources => new List() + { + new Stylesheet("_content/SZUAbsolventenverein.Module.BlackBoard/Module.css") + }; + + private ElementReference form; + private bool validated = false; + + private int _id; + private string _name; + private string _createdby; + private DateTime _createdon; + private string _modifiedby; + private DateTime _modifiedon; + + protected override async Task OnInitializedAsync() + { + try + { + if (PageState.Action == "Edit") + { + _id = Int32.Parse(PageState.QueryString["id"]); + BlackBoard BlackBoard = await BlackBoardService.GetBlackBoardAsync(_id, ModuleState.ModuleId); + if (BlackBoard != null) + { + _name = BlackBoard.Name; + _createdby = BlackBoard.CreatedBy; + _createdon = BlackBoard.CreatedOn; + _modifiedby = BlackBoard.ModifiedBy; + _modifiedon = BlackBoard.ModifiedOn; + } + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading BlackBoard {BlackBoardId} {Error}", _id, ex.Message); + AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error); + } + } + + private async Task Save() + { + try + { + validated = true; + var interop = new Oqtane.UI.Interop(JSRuntime); + if (await interop.FormValid(form)) + { + if (PageState.Action == "Add") + { + BlackBoard BlackBoard = new BlackBoard(); + BlackBoard.ModuleId = ModuleState.ModuleId; + BlackBoard.Name = _name; + BlackBoard = await BlackBoardService.AddBlackBoardAsync(BlackBoard); + await logger.LogInformation("BlackBoard Added {BlackBoard}", BlackBoard); + } + else + { + BlackBoard BlackBoard = await BlackBoardService.GetBlackBoardAsync(_id, ModuleState.ModuleId); + BlackBoard.Name = _name; + await BlackBoardService.UpdateBlackBoardAsync(BlackBoard); + await logger.LogInformation("BlackBoard Updated {BlackBoard}", BlackBoard); + } + NavigationManager.NavigateTo(NavigateUrl()); + } + else + { + AddModuleMessage(Localizer["Message.SaveValidation"], MessageType.Warning); + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Saving BlackBoard {Error}", ex.Message); + AddModuleMessage(Localizer["Message.SaveError"], MessageType.Error); + } + } + + private async Task Report() + { + try + { + BlackBoard BlackBoard = await BlackBoardService.GetBlackBoardAsync(_id, ModuleState.ModuleId); + BlackBoard.Name = _name; + ReportingHandler.Report(BlackBoard, "Reported by user"); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Reporting BlackBoard {BlackBoardId} {Error}", _id, ex.Message); + AddModuleMessage(Localizer["Message.ReportError"], MessageType.Error); + } + } +} diff --git a/Client/Modules/SZUAbsolventenverein.Module.BlackBoard/Index.razor b/Client/Modules/SZUAbsolventenverein.Module.BlackBoard/Index.razor new file mode 100644 index 0000000..c083080 --- /dev/null +++ b/Client/Modules/SZUAbsolventenverein.Module.BlackBoard/Index.razor @@ -0,0 +1,79 @@ +@using SZUAbsolventenverein.Module.BlackBoard.Services +@using SZUAbsolventenverein.Module.BlackBoard.Models + +@namespace SZUAbsolventenverein.Module.BlackBoard +@inherits ModuleBase +@inject IBlackBoardService BlackBoardService +@inject NavigationManager NavigationManager +@inject IStringLocalizer Localizer + +@if (_BlackBoards == null) +{ +

Loading...

+} +else +{ + +
+
+ @if (@_BlackBoards.Count != 0) + { + +
+   +   + @Localizer["Name"] +
+ + + + @context.Name + +
+ } + else + { +

@Localizer["Message.DisplayNone"]

+ } +} + +@code { + public override string RenderMode => RenderModes.Static; + + public override List Resources => new List() + { + new Stylesheet("_content/SZUAbsolventenverein.Module.BlackBoard/Module.css"), + new Script("_content/SZUAbsolventenverein.Module.BlackBoard/Module.js") + }; + + List _BlackBoards; + + protected override async Task OnInitializedAsync() + { + try + { + _BlackBoards = await BlackBoardService.GetBlackBoardsAsync(ModuleState.ModuleId); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading BlackBoard {Error}", ex.Message); + AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error); + } + } + + private async Task Delete(BlackBoard BlackBoard) + { + try + { + await BlackBoardService.DeleteBlackBoardAsync(BlackBoard.BlackBoardId, ModuleState.ModuleId); + await logger.LogInformation("BlackBoard Deleted {BlackBoard}", BlackBoard); + _BlackBoards = await BlackBoardService.GetBlackBoardsAsync(ModuleState.ModuleId); + StateHasChanged(); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Deleting BlackBoard {BlackBoard} {Error}", BlackBoard, ex.Message); + AddModuleMessage(Localizer["Message.DeleteError"], MessageType.Error); + } + } +} \ No newline at end of file diff --git a/Client/Modules/SZUAbsolventenverein.Module.BlackBoard/ModuleInfo.cs b/Client/Modules/SZUAbsolventenverein.Module.BlackBoard/ModuleInfo.cs new file mode 100644 index 0000000..3a8b5e2 --- /dev/null +++ b/Client/Modules/SZUAbsolventenverein.Module.BlackBoard/ModuleInfo.cs @@ -0,0 +1,19 @@ +using Oqtane.Models; +using Oqtane.Modules; + +namespace SZUAbsolventenverein.Module.BlackBoard +{ + public class ModuleInfo : IModule + { + public ModuleDefinition ModuleDefinition => new ModuleDefinition + { + Name = "BlackBoard", + Description = "Kommunikationsplatform", + Version = "1.0.0", + ServerManagerType = "SZUAbsolventenverein.Module.BlackBoard.Manager.BlackBoardManager, SZUAbsolventenverein.Module.BlackBoard.Server.Oqtane", + ReleaseVersions = "1.0.0", + Dependencies = "SZUAbsolventenverein.Module.BlackBoard.Shared.Oqtane", + PackageName = "SZUAbsolventenverein.Module.BlackBoard" + }; + } +} diff --git a/Client/Modules/SZUAbsolventenverein.Module.BlackBoard/Settings.razor b/Client/Modules/SZUAbsolventenverein.Module.BlackBoard/Settings.razor new file mode 100644 index 0000000..bbde60e --- /dev/null +++ b/Client/Modules/SZUAbsolventenverein.Module.BlackBoard/Settings.razor @@ -0,0 +1,47 @@ +@namespace SZUAbsolventenverein.Module.BlackBoard +@inherits ModuleBase +@inject ISettingService SettingService +@inject IStringLocalizer Localizer + +
+
+ +
+ +
+
+
+ +@code { + private string resourceType = "SZUAbsolventenverein.Module.BlackBoard.Settings, SZUAbsolventenverein.Module.BlackBoard.Client.Oqtane"; // for localization + public override string Title => "BlackBoard Settings"; + + string _value; + + protected override async Task OnInitializedAsync() + { + try + { + Dictionary settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId); + _value = SettingService.GetSetting(settings, "SettingName", ""); + } + catch (Exception ex) + { + AddModuleMessage(ex.Message, MessageType.Error); + } + } + + public async Task UpdateSettings() + { + try + { + var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId); + settings = SettingService.SetSetting(settings, "SettingName", _value); + await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId); + } + catch (Exception ex) + { + AddModuleMessage(ex.Message, MessageType.Error); + } + } +} diff --git a/Client/Resources/SZUAbsolventenverein.Module.BlackBoard/Edit.resx b/Client/Resources/SZUAbsolventenverein.Module.BlackBoard/Edit.resx new file mode 100644 index 0000000..3116454 --- /dev/null +++ b/Client/Resources/SZUAbsolventenverein.Module.BlackBoard/Edit.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name: + + + Enter the name + + + Save + + + Cancel + + + Error Loading BlackBoard + + + Please Provide All Required Information + + + Error Saving BlackBoard + + \ No newline at end of file diff --git a/Client/Resources/SZUAbsolventenverein.Module.BlackBoard/Index.resx b/Client/Resources/SZUAbsolventenverein.Module.BlackBoard/Index.resx new file mode 100644 index 0000000..0bfb4f0 --- /dev/null +++ b/Client/Resources/SZUAbsolventenverein.Module.BlackBoard/Index.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Add BlackBoard + + + Edit + + + Delete + + + Delete BlackBoard + + + Are You Sure You Wish To Delete This BlackBoard? + + + No BlackBoards To Display + + + Error Loading BlackBoard + + + Error Deleting BlackBoard + + \ No newline at end of file diff --git a/Client/Resources/SZUAbsolventenverein.Module.BlackBoard/Settings.resx b/Client/Resources/SZUAbsolventenverein.Module.BlackBoard/Settings.resx new file mode 100644 index 0000000..83dc88f --- /dev/null +++ b/Client/Resources/SZUAbsolventenverein.Module.BlackBoard/Settings.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name: + + + Enter a value + + \ No newline at end of file diff --git a/Client/SZUAbsolventenverein.Module.BlackBoard.Client.csproj b/Client/SZUAbsolventenverein.Module.BlackBoard.Client.csproj new file mode 100644 index 0000000..12b4362 --- /dev/null +++ b/Client/SZUAbsolventenverein.Module.BlackBoard.Client.csproj @@ -0,0 +1,41 @@ + + + + net10.0 + 1.0.0 + SZUAbsolventenverein + SZUAbsolventenverein + Kommunikationsplatform + SZUAbsolventenverein.Module.BlackBoard + SZUAbsolventenverein + SZUAbsolventenverein.Module.BlackBoard.Client.Oqtane + true + + + + + + + + + + + + + + + + + ..\..\interfaces\Interfaces\bin\Debug\net10.0\Interfaces.dll + + ..\..\oqtane.framework\Oqtane.Server\bin\Debug\net10.0\Oqtane.Client.dll + ..\..\oqtane.framework\Oqtane.Server\bin\Debug\net10.0\Oqtane.Shared.dll + + + + + false + false + + + diff --git a/Client/Services/BlackBoardService.cs b/Client/Services/BlackBoardService.cs new file mode 100644 index 0000000..f2b9004 --- /dev/null +++ b/Client/Services/BlackBoardService.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Oqtane.Services; +using Oqtane.Shared; + +namespace SZUAbsolventenverein.Module.BlackBoard.Services +{ + public interface IBlackBoardService + { + Task> GetBlackBoardsAsync(int ModuleId); + + Task GetBlackBoardAsync(int BlackBoardId, int ModuleId); + + Task AddBlackBoardAsync(Models.BlackBoard BlackBoard); + + Task UpdateBlackBoardAsync(Models.BlackBoard BlackBoard); + + Task DeleteBlackBoardAsync(int BlackBoardId, int ModuleId); + } + + public class BlackBoardService : ServiceBase, IBlackBoardService + { + public BlackBoardService(HttpClient http, SiteState siteState) : base(http, siteState) { } + + private string Apiurl => CreateApiUrl("BlackBoard"); + + public async Task> GetBlackBoardsAsync(int ModuleId) + { + List BlackBoards = await GetJsonAsync>(CreateAuthorizationPolicyUrl($"{Apiurl}?moduleid={ModuleId}", EntityNames.Module, ModuleId), Enumerable.Empty().ToList()); + return BlackBoards.OrderBy(item => item.Name).ToList(); + } + + public async Task GetBlackBoardAsync(int BlackBoardId, int ModuleId) + { + return await GetJsonAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{BlackBoardId}/{ModuleId}", EntityNames.Module, ModuleId)); + } + + public async Task AddBlackBoardAsync(Models.BlackBoard BlackBoard) + { + return await PostJsonAsync(CreateAuthorizationPolicyUrl($"{Apiurl}", EntityNames.Module, BlackBoard.ModuleId), BlackBoard); + } + + public async Task UpdateBlackBoardAsync(Models.BlackBoard BlackBoard) + { + return await PutJsonAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{BlackBoard.BlackBoardId}", EntityNames.Module, BlackBoard.ModuleId), BlackBoard); + } + + public async Task DeleteBlackBoardAsync(int BlackBoardId, int ModuleId) + { + await DeleteAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{BlackBoardId}/{ModuleId}", EntityNames.Module, ModuleId)); + } + } +} diff --git a/Client/Startup/ClientStartup.cs b/Client/Startup/ClientStartup.cs new file mode 100644 index 0000000..3a5d84b --- /dev/null +++ b/Client/Startup/ClientStartup.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.DependencyInjection; +using System.Linq; +using Oqtane.Services; +using SZUAbsolventenverein.Module.BlackBoard.Services; + +namespace SZUAbsolventenverein.Module.BlackBoard.Startup +{ + public class ClientStartup : IClientStartup + { + public void ConfigureServices(IServiceCollection services) + { + if (!services.Any(s => s.ServiceType == typeof(IBlackBoardService))) + { + services.AddScoped(); + } + } + } +} diff --git a/Client/_Imports.razor b/Client/_Imports.razor new file mode 100644 index 0000000..2f6fb1b --- /dev/null +++ b/Client/_Imports.razor @@ -0,0 +1,24 @@ +@using System +@using System.Linq +@using System.Collections.Generic +@using System.Net.Http +@using System.Net.Http.Json + +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.Extensions.Localization +@using Microsoft.JSInterop + +@using Oqtane.Models +@using Oqtane.Modules +@using Oqtane.Modules.Controls +@using Oqtane.Providers +@using Oqtane.Security +@using Oqtane.Services +@using Oqtane.Shared +@using Oqtane.Themes +@using Oqtane.Themes.Controls +@using Oqtane.UI +@using Oqtane.Enums +@using Oqtane.Interfaces \ No newline at end of file diff --git a/Package/SZUAbsolventenverein.Module.BlackBoard.Package.csproj b/Package/SZUAbsolventenverein.Module.BlackBoard.Package.csproj new file mode 100644 index 0000000..07ea216 --- /dev/null +++ b/Package/SZUAbsolventenverein.Module.BlackBoard.Package.csproj @@ -0,0 +1,29 @@ + + + + net10.0 + false + false + + + + + True + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Package/SZUAbsolventenverein.Module.BlackBoard.nuspec b/Package/SZUAbsolventenverein.Module.BlackBoard.nuspec new file mode 100644 index 0000000..aaa0a95 --- /dev/null +++ b/Package/SZUAbsolventenverein.Module.BlackBoard.nuspec @@ -0,0 +1,38 @@ + + + + $projectname$ + 1.0.0 + SZUAbsolventenverein + SZUAbsolventenverein + BlackBoard + Kommunikationsplatform + SZUAbsolventenverein + false + MIT + https://github.com/oqtane/oqtane.framework + icon.png + oqtane module + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Package/debug.cmd b/Package/debug.cmd new file mode 100644 index 0000000..cebe487 --- /dev/null +++ b/Package/debug.cmd @@ -0,0 +1,11 @@ +@echo off +set TargetFramework=%1 +set ProjectName=%2 + +XCOPY "..\Client\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.dll" "..\..\oqtane.framework\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y +XCOPY "..\Client\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.pdb" "..\..\oqtane.framework\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y +XCOPY "..\Server\bin\Debug\%TargetFramework%\%ProjectName%.Server.Oqtane.dll" "..\..\oqtane.framework\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y +XCOPY "..\Server\bin\Debug\%TargetFramework%\%ProjectName%.Server.Oqtane.pdb" "..\..\oqtane.framework\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y +XCOPY "..\Shared\bin\Debug\%TargetFramework%\%ProjectName%.Shared.Oqtane.dll" "..\..\oqtane.framework\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y +XCOPY "..\Shared\bin\Debug\%TargetFramework%\%ProjectName%.Shared.Oqtane.pdb" "..\..\oqtane.framework\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y +XCOPY "..\Server\wwwroot\*" "..\..\oqtane.framework\Oqtane.Server\wwwroot\_content\%ProjectName%\" /Y /S /I \ No newline at end of file diff --git a/Package/debug.sh b/Package/debug.sh new file mode 100644 index 0000000..058d474 --- /dev/null +++ b/Package/debug.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +TargetFramework=$1 +ProjectName=$2 + +cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName.Client.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/$TargetFramework/" +cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName.Client.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/$TargetFramework/" +cp -f "../Server/bin/Debug/$TargetFramework/$ProjectName.Server.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/$TargetFramework/" +cp -f "../Server/bin/Debug/$TargetFramework/$ProjectName.Server.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/$TargetFramework/" +cp -f "../Shared/bin/Debug/$TargetFramework/$ProjectName.Shared.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/$TargetFramework/" +cp -f "../Shared/bin/Debug/$TargetFramework/$ProjectName.Shared.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/$TargetFramework/" +cp -rf "../Server/wwwroot/"* "../../oqtane.framework/Oqtane.Server/wwwroot/_content/$ProjectName/" diff --git a/Package/icon.png b/Package/icon.png new file mode 100644 index 0000000..7422cf2 Binary files /dev/null and b/Package/icon.png differ diff --git a/Package/release.cmd b/Package/release.cmd new file mode 100644 index 0000000..9eb2046 --- /dev/null +++ b/Package/release.cmd @@ -0,0 +1,8 @@ +@echo off +set TargetFramework=%1 +set ProjectName=%2 + +del "*.nupkg" +"..\..\oqtane.framework\oqtane.package\FixProps.exe" +"..\..\oqtane.framework\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName% +XCOPY "*.nupkg" "..\..\oqtane.framework\Oqtane.Server\Packages\" /Y \ No newline at end of file diff --git a/Package/release.sh b/Package/release.sh new file mode 100644 index 0000000..443bf64 --- /dev/null +++ b/Package/release.sh @@ -0,0 +1,7 @@ +TargetFramework=$1 +ProjectName=$2 + +find . -name "*.nupkg" -delete +"..\..\oqtane.framework\oqtane.package\FixProps.exe" +"..\..\oqtane.framework\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName% +cp -f "*.nupkg" "..\..\oqtane.framework\Oqtane.Server\Packages\" \ No newline at end of file diff --git a/SZUAbsolventenverein.Module.BlackBoard.sln.DotSettings.user b/SZUAbsolventenverein.Module.BlackBoard.sln.DotSettings.user new file mode 100644 index 0000000..457f5d9 --- /dev/null +++ b/SZUAbsolventenverein.Module.BlackBoard.sln.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/SZUAbsolventenverein.Module.BlackBoard.slnx b/SZUAbsolventenverein.Module.BlackBoard.slnx new file mode 100644 index 0000000..d4f5341 --- /dev/null +++ b/SZUAbsolventenverein.Module.BlackBoard.slnx @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Server/AssemblyInfo.cs b/Server/AssemblyInfo.cs new file mode 100644 index 0000000..6ebb920 --- /dev/null +++ b/Server/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Resources; +using Microsoft.Extensions.Localization; + +[assembly: RootNamespace("SZUAbsolventenverein.Module.BlackBoard.Server")] diff --git a/Server/Controllers/BlackBoardController.cs b/Server/Controllers/BlackBoardController.cs new file mode 100644 index 0000000..d1c1464 --- /dev/null +++ b/Server/Controllers/BlackBoardController.cs @@ -0,0 +1,114 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Oqtane.Shared; +using Oqtane.Enums; +using Oqtane.Infrastructure; +using SZUAbsolventenverein.Module.BlackBoard.Services; +using Oqtane.Controllers; +using System.Net; +using System.Threading.Tasks; + +namespace SZUAbsolventenverein.Module.BlackBoard.Controllers +{ + [Route(ControllerRoutes.ApiRoute)] + public class BlackBoardController : ModuleControllerBase + { + private readonly IBlackBoardService _BlackBoardService; + + public BlackBoardController(IBlackBoardService BlackBoardService, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor) + { + _BlackBoardService = BlackBoardService; + } + + // GET: api/?moduleid=x + [HttpGet] + [Authorize(Policy = PolicyNames.ViewModule)] + public async Task> Get(string moduleid) + { + int ModuleId; + if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId)) + { + return await _BlackBoardService.GetBlackBoardsAsync(ModuleId); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized BlackBoard Get Attempt {ModuleId}", moduleid); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return null; + } + } + + // GET api//5 + [HttpGet("{id}/{moduleid}")] + [Authorize(Policy = PolicyNames.ViewModule)] + public async Task Get(int id, int moduleid) + { + Models.BlackBoard BlackBoard = await _BlackBoardService.GetBlackBoardAsync(id, moduleid); + if (BlackBoard != null && IsAuthorizedEntityId(EntityNames.Module, BlackBoard.ModuleId)) + { + return BlackBoard; + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized BlackBoard Get Attempt {BlackBoardId} {ModuleId}", id, moduleid); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return null; + } + } + + // POST api/ + [HttpPost] + [Authorize(Policy = PolicyNames.EditModule)] + public async Task Post([FromBody] Models.BlackBoard BlackBoard) + { + if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, BlackBoard.ModuleId)) + { + BlackBoard = await _BlackBoardService.AddBlackBoardAsync(BlackBoard); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized BlackBoard Post Attempt {BlackBoard}", BlackBoard); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + BlackBoard = null; + } + return BlackBoard; + } + + // PUT api//5 + [HttpPut("{id}")] + [Authorize(Policy = PolicyNames.EditModule)] + public async Task Put(int id, [FromBody] Models.BlackBoard BlackBoard) + { + if (ModelState.IsValid && BlackBoard.BlackBoardId == id && IsAuthorizedEntityId(EntityNames.Module, BlackBoard.ModuleId)) + { + BlackBoard = await _BlackBoardService.UpdateBlackBoardAsync(BlackBoard); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized BlackBoard Put Attempt {BlackBoard}", BlackBoard); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + BlackBoard = null; + } + return BlackBoard; + } + + // DELETE api//5 + [HttpDelete("{id}/{moduleid}")] + [Authorize(Policy = PolicyNames.EditModule)] + public async Task Delete(int id, int moduleid) + { + Models.BlackBoard BlackBoard = await _BlackBoardService.GetBlackBoardAsync(id, moduleid); + if (BlackBoard != null && IsAuthorizedEntityId(EntityNames.Module, BlackBoard.ModuleId)) + { + await _BlackBoardService.DeleteBlackBoardAsync(id, BlackBoard.ModuleId); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized BlackBoard Delete Attempt {BlackBoardId} {ModuleId}", id, moduleid); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } + } + } +} diff --git a/Server/Manager/BlackBoardManager.cs b/Server/Manager/BlackBoardManager.cs new file mode 100644 index 0000000..40df448 --- /dev/null +++ b/Server/Manager/BlackBoardManager.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using Oqtane.Modules; +using Oqtane.Models; +using Oqtane.Infrastructure; +using Oqtane.Interfaces; +using Oqtane.Enums; +using Oqtane.Repository; +using SZUAbsolventenverein.Module.BlackBoard.Repository; +using System.Threading.Tasks; + +namespace SZUAbsolventenverein.Module.BlackBoard.Manager +{ + public class BlackBoardManager : MigratableModuleBase, IInstallable, IPortable, ISearchable + { + private readonly IBlackBoardRepository _BlackBoardRepository; + private readonly IDBContextDependencies _DBContextDependencies; + + public BlackBoardManager(IBlackBoardRepository BlackBoardRepository, IDBContextDependencies DBContextDependencies) + { + _BlackBoardRepository = BlackBoardRepository; + _DBContextDependencies = DBContextDependencies; + } + + public bool Install(Tenant tenant, string version) + { + return Migrate(new BlackBoardContext(_DBContextDependencies), tenant, MigrationType.Up); + } + + public bool Uninstall(Tenant tenant) + { + return Migrate(new BlackBoardContext(_DBContextDependencies), tenant, MigrationType.Down); + } + + public string ExportModule(Oqtane.Models.Module module) + { + string content = ""; + List BlackBoards = _BlackBoardRepository.GetBlackBoards(module.ModuleId).ToList(); + if (BlackBoards != null) + { + content = JsonSerializer.Serialize(BlackBoards); + } + return content; + } + + public void ImportModule(Oqtane.Models.Module module, string content, string version) + { + List BlackBoards = null; + if (!string.IsNullOrEmpty(content)) + { + BlackBoards = JsonSerializer.Deserialize>(content); + } + if (BlackBoards != null) + { + foreach(var BlackBoard in BlackBoards) + { + _BlackBoardRepository.AddBlackBoard(new Models.BlackBoard { ModuleId = module.ModuleId, Name = BlackBoard.Name }); + } + } + } + + public Task> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn) + { + var searchContentList = new List(); + + foreach (var BlackBoard in _BlackBoardRepository.GetBlackBoards(pageModule.ModuleId)) + { + if (BlackBoard.ModifiedOn >= lastIndexedOn) + { + searchContentList.Add(new SearchContent + { + EntityName = "SZUAbsolventenvereinBlackBoard", + EntityId = BlackBoard.BlackBoardId.ToString(), + Title = BlackBoard.Name, + Body = BlackBoard.Name, + ContentModifiedBy = BlackBoard.ModifiedBy, + ContentModifiedOn = BlackBoard.ModifiedOn + }); + } + } + + return Task.FromResult(searchContentList); + } + } +} diff --git a/Server/Migrations/01000000_InitializeModule.cs b/Server/Migrations/01000000_InitializeModule.cs new file mode 100644 index 0000000..c4fe73d --- /dev/null +++ b/Server/Migrations/01000000_InitializeModule.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations; +using SZUAbsolventenverein.Module.BlackBoard.Migrations.EntityBuilders; +using SZUAbsolventenverein.Module.BlackBoard.Repository; + +namespace SZUAbsolventenverein.Module.BlackBoard.Migrations +{ + [DbContext(typeof(BlackBoardContext))] + [Migration("SZUAbsolventenverein.Module.BlackBoard.01.00.00.00")] + public class InitializeModule : MultiDatabaseMigration + { + public InitializeModule(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var entityBuilder = new BlackBoardEntityBuilder(migrationBuilder, ActiveDatabase); + entityBuilder.Create(); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + var entityBuilder = new BlackBoardEntityBuilder(migrationBuilder, ActiveDatabase); + entityBuilder.Drop(); + } + } +} diff --git a/Server/Migrations/EntityBuilders/BlackBoardEntityBuilder.cs b/Server/Migrations/EntityBuilders/BlackBoardEntityBuilder.cs new file mode 100644 index 0000000..6b12851 --- /dev/null +++ b/Server/Migrations/EntityBuilders/BlackBoardEntityBuilder.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations; +using Oqtane.Migrations.EntityBuilders; + +namespace SZUAbsolventenverein.Module.BlackBoard.Migrations.EntityBuilders +{ + public class BlackBoardEntityBuilder : AuditableBaseEntityBuilder + { + private const string _entityTableName = "SZUAbsolventenvereinBlackBoard"; + private readonly PrimaryKey _primaryKey = new("PK_SZUAbsolventenvereinBlackBoard", x => x.BlackBoardId); + private readonly ForeignKey _moduleForeignKey = new("FK_SZUAbsolventenvereinBlackBoard_Module", x => x.ModuleId, "Module", "ModuleId", ReferentialAction.Cascade); + + public BlackBoardEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_moduleForeignKey); + } + + protected override BlackBoardEntityBuilder BuildTable(ColumnsBuilder table) + { + BlackBoardId = AddAutoIncrementColumn(table,"BlackBoardId"); + ModuleId = AddIntegerColumn(table,"ModuleId"); + Name = AddMaxStringColumn(table,"Name"); + AddAuditableColumns(table); + return this; + } + + public OperationBuilder BlackBoardId { get; set; } + public OperationBuilder ModuleId { get; set; } + public OperationBuilder Name { get; set; } + } +} diff --git a/Server/Repository/BlackBoardContext.cs b/Server/Repository/BlackBoardContext.cs new file mode 100644 index 0000000..007171e --- /dev/null +++ b/Server/Repository/BlackBoardContext.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Http; +using Oqtane.Modules; +using Oqtane.Repository; +using Oqtane.Infrastructure; +using Oqtane.Repository.Databases.Interfaces; + +namespace SZUAbsolventenverein.Module.BlackBoard.Repository +{ + public class BlackBoardContext : DBContextBase, ITransientService, IMultiDatabase + { + public virtual DbSet BlackBoard { get; set; } + + public BlackBoardContext(IDBContextDependencies DBContextDependencies) : base(DBContextDependencies) + { + // ContextBase handles multi-tenant database connections + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity().ToTable(ActiveDatabase.RewriteName("SZUAbsolventenvereinBlackBoard")); + } + } +} diff --git a/Server/Repository/BlackBoardRepository.cs b/Server/Repository/BlackBoardRepository.cs new file mode 100644 index 0000000..2b11866 --- /dev/null +++ b/Server/Repository/BlackBoardRepository.cs @@ -0,0 +1,75 @@ +using Microsoft.EntityFrameworkCore; +using System.Linq; +using System.Collections.Generic; +using Oqtane.Modules; + +namespace SZUAbsolventenverein.Module.BlackBoard.Repository +{ + public interface IBlackBoardRepository + { + IEnumerable GetBlackBoards(int ModuleId); + Models.BlackBoard GetBlackBoard(int BlackBoardId); + Models.BlackBoard GetBlackBoard(int BlackBoardId, bool tracking); + Models.BlackBoard AddBlackBoard(Models.BlackBoard BlackBoard); + Models.BlackBoard UpdateBlackBoard(Models.BlackBoard BlackBoard); + void DeleteBlackBoard(int BlackBoardId); + } + + public class BlackBoardRepository : IBlackBoardRepository, ITransientService + { + private readonly IDbContextFactory _factory; + + public BlackBoardRepository(IDbContextFactory factory) + { + _factory = factory; + } + + public IEnumerable GetBlackBoards(int ModuleId) + { + using var db = _factory.CreateDbContext(); + return db.BlackBoard.Where(item => item.ModuleId == ModuleId).ToList(); + } + + public Models.BlackBoard GetBlackBoard(int BlackBoardId) + { + return GetBlackBoard(BlackBoardId, true); + } + + public Models.BlackBoard GetBlackBoard(int BlackBoardId, bool tracking) + { + using var db = _factory.CreateDbContext(); + if (tracking) + { + return db.BlackBoard.Find(BlackBoardId); + } + else + { + return db.BlackBoard.AsNoTracking().FirstOrDefault(item => item.BlackBoardId == BlackBoardId); + } + } + + public Models.BlackBoard AddBlackBoard(Models.BlackBoard BlackBoard) + { + using var db = _factory.CreateDbContext(); + db.BlackBoard.Add(BlackBoard); + db.SaveChanges(); + return BlackBoard; + } + + public Models.BlackBoard UpdateBlackBoard(Models.BlackBoard BlackBoard) + { + using var db = _factory.CreateDbContext(); + db.Entry(BlackBoard).State = EntityState.Modified; + db.SaveChanges(); + return BlackBoard; + } + + public void DeleteBlackBoard(int BlackBoardId) + { + using var db = _factory.CreateDbContext(); + Models.BlackBoard BlackBoard = db.BlackBoard.Find(BlackBoardId); + db.BlackBoard.Remove(BlackBoard); + db.SaveChanges(); + } + } +} diff --git a/Server/SZUAbsolventenverein.Module.BlackBoard.Server.csproj b/Server/SZUAbsolventenverein.Module.BlackBoard.Server.csproj new file mode 100644 index 0000000..ac858bc --- /dev/null +++ b/Server/SZUAbsolventenverein.Module.BlackBoard.Server.csproj @@ -0,0 +1,38 @@ + + + + net10.0 + true + 1.0.0 + SZUAbsolventenverein.Module.BlackBoard + SZUAbsolventenverein + SZUAbsolventenverein + Kommunikationsplatform + SZUAbsolventenverein + SZUAbsolventenverein.Module.BlackBoard.Server.Oqtane + true + false + + + + + + + + + + + + + + + + + + + + + ..\..\oqtane.framework\Oqtane.Server\bin\Debug\net10.0\Oqtane.Server.dll + ..\..\oqtane.framework\Oqtane.Server\bin\Debug\net10.0\Oqtane.Shared.dll + + diff --git a/Server/Services/BlackBoardService.cs b/Server/Services/BlackBoardService.cs new file mode 100644 index 0000000..57fa03b --- /dev/null +++ b/Server/Services/BlackBoardService.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Oqtane.Enums; +using Oqtane.Infrastructure; +using Oqtane.Models; +using Oqtane.Security; +using Oqtane.Shared; +using SZUAbsolventenverein.Module.BlackBoard.Repository; + +namespace SZUAbsolventenverein.Module.BlackBoard.Services +{ + public class ServerBlackBoardService : IBlackBoardService + { + private readonly IBlackBoardRepository _BlackBoardRepository; + private readonly IUserPermissions _userPermissions; + private readonly ILogManager _logger; + private readonly IHttpContextAccessor _accessor; + private readonly Alias _alias; + + public ServerBlackBoardService(IBlackBoardRepository BlackBoardRepository, IUserPermissions userPermissions, ITenantManager tenantManager, ILogManager logger, IHttpContextAccessor accessor) + { + _BlackBoardRepository = BlackBoardRepository; + _userPermissions = userPermissions; + _logger = logger; + _accessor = accessor; + _alias = tenantManager.GetAlias(); + } + + public Task> GetBlackBoardsAsync(int ModuleId) + { + if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.View)) + { + return Task.FromResult(_BlackBoardRepository.GetBlackBoards(ModuleId).ToList()); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized BlackBoard Get Attempt {ModuleId}", ModuleId); + return null; + } + } + + public Task GetBlackBoardAsync(int BlackBoardId, int ModuleId) + { + if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.View)) + { + return Task.FromResult(_BlackBoardRepository.GetBlackBoard(BlackBoardId)); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized BlackBoard Get Attempt {BlackBoardId} {ModuleId}", BlackBoardId, ModuleId); + return null; + } + } + + public Task AddBlackBoardAsync(Models.BlackBoard BlackBoard) + { + if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, BlackBoard.ModuleId, PermissionNames.Edit)) + { + BlackBoard = _BlackBoardRepository.AddBlackBoard(BlackBoard); + _logger.Log(LogLevel.Information, this, LogFunction.Create, "BlackBoard Added {BlackBoard}", BlackBoard); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized BlackBoard Add Attempt {BlackBoard}", BlackBoard); + BlackBoard = null; + } + return Task.FromResult(BlackBoard); + } + + public Task UpdateBlackBoardAsync(Models.BlackBoard BlackBoard) + { + if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, BlackBoard.ModuleId, PermissionNames.Edit)) + { + BlackBoard = _BlackBoardRepository.UpdateBlackBoard(BlackBoard); + _logger.Log(LogLevel.Information, this, LogFunction.Update, "BlackBoard Updated {BlackBoard}", BlackBoard); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized BlackBoard Update Attempt {BlackBoard}", BlackBoard); + BlackBoard = null; + } + return Task.FromResult(BlackBoard); + } + + public Task DeleteBlackBoardAsync(int BlackBoardId, int ModuleId) + { + if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit)) + { + _BlackBoardRepository.DeleteBlackBoard(BlackBoardId); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "BlackBoard Deleted {BlackBoardId}", BlackBoardId); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized BlackBoard Delete Attempt {BlackBoardId} {ModuleId}", BlackBoardId, ModuleId); + } + return Task.CompletedTask; + } + } +} diff --git a/Server/Startup/ServerStartup.cs b/Server/Startup/ServerStartup.cs new file mode 100644 index 0000000..12d35c5 --- /dev/null +++ b/Server/Startup/ServerStartup.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Oqtane.Infrastructure; +using SZUAbsolventenverein.Module.BlackBoard.Repository; +using SZUAbsolventenverein.Module.BlackBoard.Services; + +namespace SZUAbsolventenverein.Module.BlackBoard.Startup +{ + public class ServerStartup : IServerStartup + { + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + // not implemented + } + + public void ConfigureMvc(IMvcBuilder mvcBuilder) + { + // not implemented + } + + public void ConfigureServices(IServiceCollection services) + { + services.AddTransient(); + services.AddDbContextFactory(opt => { }, ServiceLifetime.Transient); + } + } +} diff --git a/Server/wwwroot/Module.css b/Server/wwwroot/Module.css new file mode 100644 index 0000000..0856a26 --- /dev/null +++ b/Server/wwwroot/Module.css @@ -0,0 +1 @@ +/* Module Custom Styles */ \ No newline at end of file diff --git a/Server/wwwroot/Module.js b/Server/wwwroot/Module.js new file mode 100644 index 0000000..cd3f6d7 --- /dev/null +++ b/Server/wwwroot/Module.js @@ -0,0 +1,5 @@ +/* Module Script */ +var SZUAbsolventenverein = SZUAbsolventenverein || {}; + +SZUAbsolventenverein.BlackBoard = { +}; \ No newline at end of file diff --git a/Shared/Models/BlackBoard.cs b/Shared/Models/BlackBoard.cs new file mode 100644 index 0000000..8d4040e --- /dev/null +++ b/Shared/Models/BlackBoard.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Interfaces; +using Oqtane.Models; + +namespace SZUAbsolventenverein.Module.BlackBoard.Models +{ + [Table("SZUAbsolventenvereinBlackBoard")] + public class BlackBoard : ModelBase, IReportable + { + [Key] + public int BlackBoardId { get; set; } + public int ModuleId { get; set; } + public string Name { get; set; } + + [NotMapped] + public string ModuleName => "BlackBoard"; + [NotMapped] + public int ModuleID => ModuleId; + [NotMapped] + public int EntityID => BlackBoardId; + } +} diff --git a/Shared/SZUAbsolventenverein.Module.BlackBoard.Shared.csproj b/Shared/SZUAbsolventenverein.Module.BlackBoard.Shared.csproj new file mode 100644 index 0000000..95ef6fc --- /dev/null +++ b/Shared/SZUAbsolventenverein.Module.BlackBoard.Shared.csproj @@ -0,0 +1,25 @@ + + + + net10.0 + 1.0.0 + SZUAbsolventenverein.Module.BlackBoard + SZUAbsolventenverein + SZUAbsolventenverein + Kommunikationsplatform + SZUAbsolventenverein + SZUAbsolventenverein.Module.BlackBoard.Shared.Oqtane + + + + + + + + + ..\..\interfaces\Interfaces\bin\Debug\net10.0\Interfaces.dll + + ..\..\oqtane.framework\Oqtane.Server\bin\Debug\net10.0\Oqtane.Shared.dll + + + diff --git a/exteneral.module.template.json b/exteneral.module.template.json new file mode 100644 index 0000000..2656ab0 --- /dev/null +++ b/exteneral.module.template.json @@ -0,0 +1,6 @@ +{ + "Title": "Default Module Template", + "Type": "External", + "Version": "10.0.0", + "Namespace": "SZUAbsolventenverein.Module.BlackBoard" +}