diff --git a/Frontend/fonts/utheem.ttf b/Frontend/fonts/utheem.ttf
new file mode 100644
index 0000000..2f244dc
Binary files /dev/null and b/Frontend/fonts/utheem.ttf differ
diff --git a/Frontend/index.html b/Frontend/index.html
new file mode 100644
index 0000000..eb1cfcd
--- /dev/null
+++ b/Frontend/index.html
@@ -0,0 +1,408 @@
+
+
+
+
+
+ Petition Details
+
+
+
+
+
+
+
Loading petition...
+
+
+
+
+
+
+
+
+
+
+
Author Details
+
ލިޔުންތެރިގެ މައުލޫމާތު
+
Name:ނަން:
+
+
+
+
+
+
+
Sign this Petition
+
މި މައްސަލައިގައި ސޮއި ކުރައްވާ
+
+
+
+
+
+
+
+
+
diff --git a/Frontend/style.css b/Frontend/style.css
new file mode 100644
index 0000000..ba5395a
--- /dev/null
+++ b/Frontend/style.css
@@ -0,0 +1,334 @@
+@font-face {
+ font-family: 'Utheem';
+ src: url('fonts/utheem.woff') format('woff'),
+ url('fonts/utheem.ttf') format('truetype');
+ font-weight: normal;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Shangu';
+ src: url('fonts/shangu.woff') format('woff'),
+ url('fonts/shangu.ttf') format('truetype');
+ font-weight: normal;
+ font-style: normal;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+ background-color: #f5f5f5;
+ color: #333;
+ line-height: 1.6;
+ padding: 20px;
+}
+
+.container {
+ max-width: 900px;
+ margin: 0 auto;
+ background-color: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ padding: 40px;
+}
+
+.lang-switcher {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+ margin-bottom: 20px;
+}
+
+.lang-btn {
+ padding: 8px 20px;
+ border: 2px solid #007bff;
+ background-color: white;
+ color: #007bff;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: 500;
+ transition: all 0.3s ease;
+}
+
+.lang-btn:hover {
+ background-color: #f0f8ff;
+}
+
+.lang-btn.active {
+ background-color: #007bff;
+ color: white;
+}
+
+.lang-btn:focus {
+ outline: none;
+ box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
+}
+
+.loading {
+ text-align: center;
+ font-size: 18px;
+ color: #666;
+ padding: 40px;
+}
+
+.error {
+ background-color: #fee;
+ border: 1px solid #fcc;
+ color: #c33;
+ padding: 20px;
+ border-radius: 4px;
+ margin-bottom: 20px;
+}
+
+.petition-header {
+ border-bottom: 2px solid #007bff;
+ padding-bottom: 20px;
+ margin-bottom: 30px;
+}
+
+.petition-header h1 {
+ font-size: 32px;
+ color: #222;
+ margin-bottom: 10px;
+}
+
+.petition-header h2 {
+ font-size: 24px;
+ color: #555;
+ margin-bottom: 15px;
+}
+
+.petition-header h2.dhivehi {
+ font-family: 'Shangu', 'Faruma', 'MV Faseyha', 'Waheed', 'Noto Sans Thaana', sans-serif;
+}
+
+.dhivehi {
+ font-family: 'Utheem', 'Faruma', 'MV Faseyha', 'Waheed', 'Noto Sans Thaana', sans-serif;
+ direction: rtl;
+ text-align: right;
+}
+
+.metadata {
+ display: flex;
+ gap: 20px;
+ flex-wrap: wrap;
+ margin-top: 15px;
+ font-size: 14px;
+ color: #666;
+}
+
+.metadata span {
+ background-color: #f0f0f0;
+ padding: 6px 12px;
+ border-radius: 4px;
+}
+
+.metadata span span {
+ background-color: transparent;
+ padding: 0;
+ font-weight: bold;
+ color: #333;
+}
+
+.author-details {
+ background-color: #f9f9f9;
+ padding: 20px;
+ border-radius: 6px;
+ margin-bottom: 30px;
+}
+
+.author-details h3 {
+ font-size: 18px;
+ color: #007bff;
+ margin-bottom: 12px;
+}
+
+.author-details p {
+ margin-bottom: 8px;
+}
+
+.petition-body {
+ margin-top: 30px;
+}
+
+.petition-body h3 {
+ font-size: 20px;
+ color: #007bff;
+ margin-top: 30px;
+ margin-bottom: 15px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #e0e0e0;
+}
+
+.body-content {
+ padding: 15px;
+ background-color: #fafafa;
+ border-radius: 4px;
+ line-height: 1.8;
+ white-space: pre-wrap;
+}
+
+.body-content strong {
+ color: #222;
+}
+
+.signature-count {
+ font-weight: bold;
+}
+
+.signature-count span {
+ color: #007bff !important;
+}
+
+.signature-section {
+ margin-top: 40px;
+ padding-top: 30px;
+ border-top: 2px solid #e0e0e0;
+}
+
+.signature-section h3 {
+ font-size: 24px;
+ color: #007bff;
+ margin-bottom: 25px;
+}
+
+.form-group {
+ margin-bottom: 20px;
+}
+
+.form-group label {
+ display: block;
+ font-weight: 600;
+ margin-bottom: 8px;
+ color: #333;
+}
+
+.form-group input[type="text"] {
+ width: 100%;
+ padding: 12px;
+ border: 2px solid #ddd;
+ border-radius: 6px;
+ font-size: 16px;
+ transition: border-color 0.3s ease;
+}
+
+.form-group input[type="text"]:focus {
+ outline: none;
+ border-color: #007bff;
+}
+
+.signature-pad-container {
+ border: 2px solid #ddd;
+ border-radius: 6px;
+ background-color: white;
+ display: inline-block;
+ cursor: crosshair;
+ margin-bottom: 10px;
+}
+
+#signature-pad {
+ display: block;
+ touch-action: none;
+}
+
+.signature-actions {
+ margin-bottom: 15px;
+}
+
+.form-buttons {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.btn-primary,
+.btn-secondary {
+ padding: 12px 24px;
+ border: none;
+ border-radius: 6px;
+ font-size: 16px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.btn-primary {
+ background-color: #007bff;
+ color: white;
+}
+
+.btn-primary:hover {
+ background-color: #0056b3;
+}
+
+.btn-secondary {
+ background-color: #6c757d;
+ color: white;
+}
+
+.btn-secondary:hover {
+ background-color: #545b62;
+}
+
+.form-message {
+ padding: 12px;
+ border-radius: 6px;
+ margin-bottom: 15px;
+ display: none;
+}
+
+.form-message.success {
+ background-color: #d4edda;
+ border: 1px solid #c3e6cb;
+ color: #155724;
+}
+
+.form-message.error {
+ background-color: #f8d7da;
+ border: 1px solid #f5c6cb;
+ color: #721c24;
+}
+
+@media (max-width: 768px) {
+ .container {
+ padding: 20px;
+ }
+
+ .petition-header h1 {
+ font-size: 24px;
+ }
+
+ .petition-header h2 {
+ font-size: 18px;
+ }
+
+ .metadata {
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ #signature-pad {
+ width: 100%;
+ max-width: 100%;
+ }
+
+ .signature-pad-container {
+ width: 100%;
+ overflow-x: auto;
+ }
+
+ .form-buttons {
+ flex-direction: column;
+ width: 100%;
+ }
+
+ .form-buttons button {
+ width: 100%;
+ }
+}
diff --git a/README.md b/README.md
index 17d7361..d72f328 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,10 @@
# WPetition Submission API
+
+a self hostable e petition system to collect signatures for your cause.
+
+## why make this
maldives parliment promised the release of a e-petition system powered by efass will be released months ago and then never released it
i said fuck it i want data protection bill so i made this simple signature collection system since the law doesnt care if youre signature is signed digitally or via wet ink.
-made it in 5 hours and didnt even vibe code it
## nerd shit
A petition signing API built with ASP.NET Core 9.0 that allows users to sign petitions and retrieve petition details. Features rate limiting to prevent spam and duplicate signature detection.
@@ -26,11 +29,7 @@ A petition signing API built with ASP.NET Core 9.0 that allows users to sign pet
### MongoDB Setup
-1. Create a MongoDB database for the petition system
-2. Create the following collections:
- - `signatures` - stores petition signatures
- - `petitions` - stores petition details
- - `authors` - stores petition author information
+refer to the details below
### Application Configuration
@@ -38,6 +37,9 @@ Update `appsettings.json` with your MongoDB connection settings:
```json
{
+ "PetitionSettings": {
+ "AllowPetitionCreation": true
+ },
"MongoDbSettings": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "petition_database"
@@ -52,6 +54,10 @@ Update `appsettings.json` with your MongoDB connection settings:
}
```
+by default `AllowPetitionCreation` is true. you must upload your Petition to the debug controller and then shut down the server and set this value to false and reboot or anyone will be able to submit petitions.
+
+check out `sample.Petition.md` on how to structure your petition so it will be accepted by the server
+
### Rate Limiting Configuration
The API is configured with rate limiting to prevent spam. Default settings in `Program.cs`:
@@ -197,41 +203,6 @@ Retrieves details of a specific petition including author information.
{}
```
-## Data Models
-
-### Signature (Widget)
-```csharp
-{
- "id": "ObjectId",
- "name": "string",
- "idCard": "string",
- "signature_SVG": "string",
- "timestamp": "DateTime"
-}
-```
-
-### Petition Details
-```csharp
-{
- "id": "Guid",
- "startDate": "DateOnly",
- "nameDhiv": "string",
- "nameEng": "string",
- "petitionBodyDhiv": "string",
- "petitionBodyEng": "string",
- "authorId": "Guid",
- "signatureCount": "int"
-}
-```
-
-### Author
-```csharp
-{
- "id": "Guid",
- "name": "string",
- "nid": "string"
-}
-```
## Security Features
diff --git a/Submission.Api/Configuration/PetitionSettings.cs b/Submission.Api/Configuration/PetitionSettings.cs
new file mode 100644
index 0000000..bcb7336
--- /dev/null
+++ b/Submission.Api/Configuration/PetitionSettings.cs
@@ -0,0 +1,6 @@
+namespace Submission.Api.Configuration;
+
+public class PetitionSettings
+{
+ public bool AllowPetitionCreation { get; set; }
+}
diff --git a/Submission.Api/Controllers/DebugController.cs b/Submission.Api/Controllers/DebugController.cs
new file mode 100644
index 0000000..fa2389c
--- /dev/null
+++ b/Submission.Api/Controllers/DebugController.cs
@@ -0,0 +1,241 @@
+using Ashi.MongoInterface.Service;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using Submission.Api.Configuration;
+using Submission.Api.Models;
+using System.Globalization;
+using YamlDotNet.Serialization;
+using YamlDotNet.Serialization.NamingConventions;
+
+namespace Submission.Api.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class DebugController : ControllerBase
+ {
+ private readonly PetitionSettings _petitionSettings;
+ private readonly IMongoRepository _authorRepository;
+ private readonly IMongoRepository _petitionRepository;
+
+ public DebugController(
+ IOptions petitionSettings,
+ IMongoRepository authorRepository,
+ IMongoRepository petitionRepository)
+ {
+ _petitionSettings = petitionSettings.Value;
+ _authorRepository = authorRepository;
+ _petitionRepository = petitionRepository;
+ }
+
+ [HttpGet("petitions", Name = "GetPetitions")]
+ public IActionResult GetPetitions()
+ {
+ try
+ {
+ var files = Directory.EnumerateFiles("Petitions");
+ return Ok(files);
+ }
+ catch (Exception e)
+ {
+ return Problem("Petitions Folder not found");
+ }
+
+ }
+
+
+ [HttpGet("create-petition-folder", Name = "CreatePetitionFolder")]
+ public IActionResult create_petition_folder()
+ {
+ try
+ {
+ Directory.CreateDirectory("Petitions");
+ return Ok("Petitions folder created");
+ }
+ catch (Exception e)
+ {
+ return Problem(e.Message);
+ }
+ }
+
+ [HttpPost("upload-petition", Name = "UploadPetition")]
+ public async Task UploadPetition(IFormFile file)
+ {
+ // Check if petition creation is allowed
+ if (!_petitionSettings.AllowPetitionCreation)
+ {
+ return StatusCode(403, new { message = "Petition creation is disabled. Set 'PetitionSettings:AllowPetitionCreation' to true in appsettings.json" });
+ }
+
+ // Validate file exists
+ if (file == null || file.Length == 0)
+ {
+ return BadRequest(new { message = "No file uploaded" });
+ }
+
+ // Validate file extension
+ if (!file.FileName.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
+ {
+ return BadRequest(new { message = "Only .md files are allowed" });
+ }
+
+ try
+ {
+ // Read file content
+ string fileContent;
+ using (var reader = new StreamReader(file.OpenReadStream()))
+ {
+ fileContent = await reader.ReadToEndAsync();
+ }
+
+ // Parse frontmatter and body
+ var (frontmatter, body) = ParseMarkdownFile(fileContent);
+
+ if (frontmatter == null)
+ {
+ return BadRequest(new { message = "Invalid markdown format. Frontmatter is required." });
+ }
+
+ // Parse YAML frontmatter
+ var deserializer = new DeserializerBuilder()
+ .WithNamingConvention(CamelCaseNamingConvention.Instance)
+ .Build();
+
+ var metadata = deserializer.Deserialize>(frontmatter);
+
+ // Extract values
+ var petitionId = Guid.NewGuid();
+ var startDateStr = metadata["startDate"].ToString();
+ var nameDhiv = metadata["nameDhiv"].ToString();
+ var nameEng = metadata["nameEng"].ToString();
+ var authorData = metadata["author"] as Dictionary