Initial Commit: BlackBoard inkl. Reporting

This commit is contained in:
2026-02-12 19:35:17 +01:00
commit 0b82942569
38 changed files with 1564 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.idea
bin/
debug/

4
Client/AssemblyInfo.cs Normal file
View File

@@ -0,0 +1,4 @@
using System.Resources;
using Microsoft.Extensions.Localization;
[assembly: RootNamespace("SZUAbsolventenverein.Module.BlackBoard.Client")]

15
Client/Interop.cs Normal file
View File

@@ -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;
}
}
}

View File

@@ -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<Edit> Localizer
<form @ref="form" class="@(validated ? " was-validated" : "needs-validation" )" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter a name" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" required />
</div>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="Save">@Localizer["Save"]</button>
<button type="button" class="btn btn-danger" @onclick="Report">@Localizer["Report"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<br /><br />
@if (PageState.Action == "Edit")
{
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
}
</form>
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Actions => "Add,Edit";
public override string Title => "Manage BlackBoard";
public override List<Resource> Resources => new List<Resource>()
{
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);
}
}
}

View File

@@ -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<Index> Localizer
@if (_BlackBoards == null)
{
<p><em>Loading...</em></p>
}
else
{
<ActionLink Action="Add" Security="SecurityAccessLevel.Edit" Text="Add BlackBoard" ResourceKey="Add" />
<br />
<br />
@if (@_BlackBoards.Count != 0)
{
<Pager Items="@_BlackBoards">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.BlackBoardId.ToString())" ResourceKey="Edit" /></td>
<td><ActionDialog Header="Delete BlackBoard" Message="Are You Sure You Wish To Delete This BlackBoard?" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" ResourceKey="Delete" Id="@context.BlackBoardId.ToString()" /></td>
<td>@context.Name</td>
</Row>
</Pager>
}
else
{
<p>@Localizer["Message.DisplayNone"]</p>
}
}
@code {
public override string RenderMode => RenderModes.Static;
public override List<Resource> Resources => new List<Resource>()
{
new Stylesheet("_content/SZUAbsolventenverein.Module.BlackBoard/Module.css"),
new Script("_content/SZUAbsolventenverein.Module.BlackBoard/Module.js")
};
List<BlackBoard> _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);
}
}
}

View File

@@ -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"
};
}
}

View File

@@ -0,0 +1,47 @@
@namespace SZUAbsolventenverein.Module.BlackBoard
@inherits ModuleBase
@inject ISettingService SettingService
@inject IStringLocalizer<Settings> Localizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="value" HelpText="Enter a value" ResourceKey="SettingName" ResourceType="@resourceType">Name: </Label>
<div class="col-sm-9">
<input id="value" type="text" class="form-control" @bind="@_value" />
</div>
</div>
</div>
@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<string, string> 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);
}
}
}

View File

@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Name.Text" xml:space="preserve">
<value>Name: </value>
</data>
<data name="Name.HelpText" xml:space="preserve">
<value>Enter the name</value>
</data>
<data name="Save" xml:space="preserve">
<value>Save</value>
</data>
<data name="Cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="Message.LoadError" xml:space="preserve">
<value>Error Loading BlackBoard</value>
</data>
<data name="Message.SaveValidation" xml:space="preserve">
<value>Please Provide All Required Information</value>
</data>
<data name="Message.SaveError" xml:space="preserve">
<value>Error Saving BlackBoard</value>
</data>
</root>

View File

@@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Name" xml:space="preserve">
<value>Name</value>
</data>
<data name="Add.Text" xml:space="preserve">
<value>Add BlackBoard</value>
</data>
<data name="Edit.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Delete.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Delete.Header" xml:space="preserve">
<value>Delete BlackBoard</value>
</data>
<data name="Delete.Message" xml:space="preserve">
<value>Are You Sure You Wish To Delete This BlackBoard?</value>
</data>
<data name="Message.DisplayNone" xml:space="preserve">
<value>No BlackBoards To Display</value>
</data>
<data name="Message.LoadError" xml:space="preserve">
<value>Error Loading BlackBoard</value>
</data>
<data name="Message.DeleteError" xml:space="preserve">
<value>Error Deleting BlackBoard</value>
</data>
</root>

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="SettingName.Text" xml:space="preserve">
<value>Name: </value>
</data>
<data name="SettingName.HelpText" xml:space="preserve">
<value>Enter a value</value>
</data>
</root>

View File

@@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Version>1.0.0</Version>
<Authors>SZUAbsolventenverein</Authors>
<Company>SZUAbsolventenverein</Company>
<Description>Kommunikationsplatform</Description>
<Product>SZUAbsolventenverein.Module.BlackBoard</Product>
<Copyright>SZUAbsolventenverein</Copyright>
<AssemblyName>SZUAbsolventenverein.Module.BlackBoard.Client.Oqtane</AssemblyName>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.1" />
<PackageReference Include="System.Net.Http.Json" Version="10.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\SZUAbsolventenverein.Module.BlackBoard.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Interfaces">
<HintPath>..\..\interfaces\Interfaces\bin\Debug\net10.0\Interfaces.dll</HintPath>
</Reference>
<Reference Include="Oqtane.Client"><HintPath>..\..\oqtane.framework\Oqtane.Server\bin\Debug\net10.0\Oqtane.Client.dll</HintPath></Reference>
<Reference Include="Oqtane.Shared"><HintPath>..\..\oqtane.framework\Oqtane.Server\bin\Debug\net10.0\Oqtane.Shared.dll</HintPath></Reference>
</ItemGroup>
<PropertyGroup>
<!-- there may be other elements here -->
<BlazorWebAssemblyEnableLinking>false</BlazorWebAssemblyEnableLinking>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup>
</Project>

View File

@@ -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<List<Models.BlackBoard>> GetBlackBoardsAsync(int ModuleId);
Task<Models.BlackBoard> GetBlackBoardAsync(int BlackBoardId, int ModuleId);
Task<Models.BlackBoard> AddBlackBoardAsync(Models.BlackBoard BlackBoard);
Task<Models.BlackBoard> 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<List<Models.BlackBoard>> GetBlackBoardsAsync(int ModuleId)
{
List<Models.BlackBoard> BlackBoards = await GetJsonAsync<List<Models.BlackBoard>>(CreateAuthorizationPolicyUrl($"{Apiurl}?moduleid={ModuleId}", EntityNames.Module, ModuleId), Enumerable.Empty<Models.BlackBoard>().ToList());
return BlackBoards.OrderBy(item => item.Name).ToList();
}
public async Task<Models.BlackBoard> GetBlackBoardAsync(int BlackBoardId, int ModuleId)
{
return await GetJsonAsync<Models.BlackBoard>(CreateAuthorizationPolicyUrl($"{Apiurl}/{BlackBoardId}/{ModuleId}", EntityNames.Module, ModuleId));
}
public async Task<Models.BlackBoard> AddBlackBoardAsync(Models.BlackBoard BlackBoard)
{
return await PostJsonAsync<Models.BlackBoard>(CreateAuthorizationPolicyUrl($"{Apiurl}", EntityNames.Module, BlackBoard.ModuleId), BlackBoard);
}
public async Task<Models.BlackBoard> UpdateBlackBoardAsync(Models.BlackBoard BlackBoard)
{
return await PutJsonAsync<Models.BlackBoard>(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));
}
}
}

View File

@@ -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<IBlackBoardService, BlackBoardService>();
}
}
}
}

24
Client/_Imports.razor Normal file
View File

@@ -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

View File

@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<AccelerateBuildsInVisualStudio>false</AccelerateBuildsInVisualStudio>
</PropertyGroup>
<ItemGroup>
<None Include="icon.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Client\SZUAbsolventenverein.Module.BlackBoard.Client.csproj" />
<ProjectReference Include="..\Server\SZUAbsolventenverein.Module.BlackBoard.Server.csproj" />
<ProjectReference Include="..\Shared\SZUAbsolventenverein.Module.BlackBoard.Shared.csproj" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="debug.cmd $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="bash $(ProjectDir)debug.sh $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Release'" Command="release.cmd $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Release'" Command="bash $(ProjectDir)release.sh $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
</Target>
</Project>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>$projectname$</id>
<version>1.0.0</version>
<authors>SZUAbsolventenverein</authors>
<owners>SZUAbsolventenverein</owners>
<title>BlackBoard</title>
<description>Kommunikationsplatform</description>
<copyright>SZUAbsolventenverein</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<icon>icon.png</icon>
<tags>oqtane module</tags>
<releaseNotes></releaseNotes>
<summary></summary>
<packageTypes>
<packageType name="Dependency" />
<packageType name="Oqtane.Framework" version="10.0.3" />
</packageTypes>
</metadata>
<files>
<file src="..\Client\bin\Release\$targetframework$\$ProjectName$.Client.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Client\bin\Release\$targetframework$\$ProjectName$.Client.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Server\bin\Release\$targetframework$\$ProjectName$.Server.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Server\bin\Release\$targetframework$\$ProjectName$.Server.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Shared\bin\Release\$targetframework$\$ProjectName$.Shared.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Shared\bin\Release\$targetframework$\$ProjectName$.Shared.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Server\obj\Release\$targetframework$\staticwebassets\msbuild.$ProjectName$.Microsoft.AspNetCore.StaticWebAssetEndpoints.props" target="build\Microsoft.AspNetCore.StaticWebAssetEndpoints.props" />
<file src="..\Server\obj\Release\$targetframework$\staticwebassets\msbuild.$ProjectName$.Microsoft.AspNetCore.StaticWebAssets.props" target="build\Microsoft.AspNetCore.StaticWebAssets.props" />
<file src="..\Server\obj\Release\$targetframework$\staticwebassets\msbuild.build.$ProjectName$.props" target="build\$ProjectName$.props" />
<file src="..\Server\obj\Release\$targetframework$\staticwebassets\msbuild.buildMultiTargeting.$ProjectName$.props" target="buildMultiTargeting\$ProjectName$.props" />
<file src="..\Server\obj\Release\$targetframework$\staticwebassets\msbuild.buildTransitive.$ProjectName$.props" target="buildTransitive\$ProjectName$.props" />
<file src="..\Server\wwwroot\**\*.*" target="staticwebassets" />
<file src="icon.png" target="" />
</files>
</package>

11
Package/debug.cmd Normal file
View File

@@ -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

12
Package/debug.sh Normal file
View File

@@ -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/"

BIN
Package/icon.png Normal file

Binary file not shown.

8
Package/release.cmd Normal file
View File

@@ -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

7
Package/release.sh Normal file
View File

@@ -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\"

View File

@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=_002Fhome_002Fkocoder_002Falumnihub_005F10_002E0_005Famd64_002Finterfaces_002FInterfaces_002Fbin_002FDebug_002Fnet10_002E0_002FInterfaces_002Edll/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -0,0 +1,9 @@
<Solution>
<Project Path="..\oqtane.framework\Oqtane.Server\Oqtane.Server.csproj" DefaultStartup="true">
<Build Solution="Debug|*" Project="false" />
</Project>
<Project Path="Client\SZUAbsolventenverein.Module.BlackBoard.Client.csproj" />
<Project Path="Server\SZUAbsolventenverein.Module.BlackBoard.Server.csproj" />
<Project Path="Shared\SZUAbsolventenverein.Module.BlackBoard.Shared.csproj" />
<Project Path="Package\SZUAbsolventenverein.Module.BlackBoard.Package.csproj" />
</Solution>

4
Server/AssemblyInfo.cs Normal file
View File

@@ -0,0 +1,4 @@
using System.Resources;
using Microsoft.Extensions.Localization;
[assembly: RootNamespace("SZUAbsolventenverein.Module.BlackBoard.Server")]

View File

@@ -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/<controller>?moduleid=x
[HttpGet]
[Authorize(Policy = PolicyNames.ViewModule)]
public async Task<IEnumerable<Models.BlackBoard>> 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/<controller>/5
[HttpGet("{id}/{moduleid}")]
[Authorize(Policy = PolicyNames.ViewModule)]
public async Task<Models.BlackBoard> 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/<controller>
[HttpPost]
[Authorize(Policy = PolicyNames.EditModule)]
public async Task<Models.BlackBoard> 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/<controller>/5
[HttpPut("{id}")]
[Authorize(Policy = PolicyNames.EditModule)]
public async Task<Models.BlackBoard> 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/<controller>/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;
}
}
}
}

View File

@@ -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<Models.BlackBoard> 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<Models.BlackBoard> BlackBoards = null;
if (!string.IsNullOrEmpty(content))
{
BlackBoards = JsonSerializer.Deserialize<List<Models.BlackBoard>>(content);
}
if (BlackBoards != null)
{
foreach(var BlackBoard in BlackBoards)
{
_BlackBoardRepository.AddBlackBoard(new Models.BlackBoard { ModuleId = module.ModuleId, Name = BlackBoard.Name });
}
}
}
public Task<List<SearchContent>> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn)
{
var searchContentList = new List<SearchContent>();
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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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<BlackBoardEntityBuilder>
{
private const string _entityTableName = "SZUAbsolventenvereinBlackBoard";
private readonly PrimaryKey<BlackBoardEntityBuilder> _primaryKey = new("PK_SZUAbsolventenvereinBlackBoard", x => x.BlackBoardId);
private readonly ForeignKey<BlackBoardEntityBuilder> _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<AddColumnOperation> BlackBoardId { get; set; }
public OperationBuilder<AddColumnOperation> ModuleId { get; set; }
public OperationBuilder<AddColumnOperation> Name { get; set; }
}
}

View File

@@ -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<Models.BlackBoard> 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<Models.BlackBoard>().ToTable(ActiveDatabase.RewriteName("SZUAbsolventenvereinBlackBoard"));
}
}
}

View File

@@ -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<Models.BlackBoard> 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<BlackBoardContext> _factory;
public BlackBoardRepository(IDbContextFactory<BlackBoardContext> factory)
{
_factory = factory;
}
public IEnumerable<Models.BlackBoard> 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();
}
}
}

View File

@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<Version>1.0.0</Version>
<Product>SZUAbsolventenverein.Module.BlackBoard</Product>
<Authors>SZUAbsolventenverein</Authors>
<Company>SZUAbsolventenverein</Company>
<Description>Kommunikationsplatform</Description>
<Copyright>SZUAbsolventenverein</Copyright>
<AssemblyName>SZUAbsolventenverein.Module.BlackBoard.Server.Oqtane</AssemblyName>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<StaticWebAssetsFingerprintContent>false</StaticWebAssetsFingerprintContent>
</PropertyGroup>
<ItemGroup>
<Content Remove="wwwroot\_content\**\*.*" />
<None Include="wwwroot\_content\**\*.*" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Client\SZUAbsolventenverein.Module.BlackBoard.Client.csproj" />
<ProjectReference Include="..\Shared\SZUAbsolventenverein.Module.BlackBoard.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Oqtane.Server"><HintPath>..\..\oqtane.framework\Oqtane.Server\bin\Debug\net10.0\Oqtane.Server.dll</HintPath></Reference>
<Reference Include="Oqtane.Shared"><HintPath>..\..\oqtane.framework\Oqtane.Server\bin\Debug\net10.0\Oqtane.Shared.dll</HintPath></Reference>
</ItemGroup>
</Project>

View File

@@ -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<List<Models.BlackBoard>> 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<Models.BlackBoard> 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<Models.BlackBoard> 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<Models.BlackBoard> 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;
}
}
}

View File

@@ -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<IBlackBoardService, ServerBlackBoardService>();
services.AddDbContextFactory<BlackBoardContext>(opt => { }, ServiceLifetime.Transient);
}
}
}

View File

@@ -0,0 +1 @@
/* Module Custom Styles */

5
Server/wwwroot/Module.js Normal file
View File

@@ -0,0 +1,5 @@
/* Module Script */
var SZUAbsolventenverein = SZUAbsolventenverein || {};
SZUAbsolventenverein.BlackBoard = {
};

View File

@@ -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;
}
}

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Version>1.0.0</Version>
<Product>SZUAbsolventenverein.Module.BlackBoard</Product>
<Authors>SZUAbsolventenverein</Authors>
<Company>SZUAbsolventenverein</Company>
<Description>Kommunikationsplatform</Description>
<Copyright>SZUAbsolventenverein</Copyright>
<AssemblyName>SZUAbsolventenverein.Module.BlackBoard.Shared.Oqtane</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="Interfaces">
<HintPath>..\..\interfaces\Interfaces\bin\Debug\net10.0\Interfaces.dll</HintPath>
</Reference>
<Reference Include="Oqtane.Shared"><HintPath>..\..\oqtane.framework\Oqtane.Server\bin\Debug\net10.0\Oqtane.Shared.dll</HintPath></Reference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
{
"Title": "Default Module Template",
"Type": "External",
"Version": "10.0.0",
"Namespace": "SZUAbsolventenverein.Module.BlackBoard"
}