From 0d763ea736d451ca2e41395d72f58ff297ec86c8 Mon Sep 17 00:00:00 2001 From: fISHIE <83373559+WhoIsFishie@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:09:05 +0500 Subject: [PATCH] i did that shit --- Frontend/fonts/utheem.ttf | Bin 0 -> 70292 bytes Frontend/index.html | 408 ++++++++++++++++++ Frontend/style.css | 334 ++++++++++++++ README.md | 53 +-- .../Configuration/PetitionSettings.cs | 6 + Submission.Api/Controllers/DebugController.cs | 241 +++++++++++ Submission.Api/Controllers/SignController.cs | 63 ++- .../Models/{Widget.cs => Signature.cs} | 4 +- Submission.Api/Program.cs | 13 +- Submission.Api/Properties/launchSettings.json | 2 +- Submission.Api/Submission.Api.csproj | 3 +- Submission.Api/Submission.Api.csproj.user | 7 +- Submission.Api/appsettings.json | 7 + sample.Petition.md | 17 + 14 files changed, 1087 insertions(+), 71 deletions(-) create mode 100644 Frontend/fonts/utheem.ttf create mode 100644 Frontend/index.html create mode 100644 Frontend/style.css create mode 100644 Submission.Api/Configuration/PetitionSettings.cs create mode 100644 Submission.Api/Controllers/DebugController.cs rename Submission.Api/Models/{Widget.cs => Signature.cs} (78%) create mode 100644 sample.Petition.md diff --git a/Frontend/fonts/utheem.ttf b/Frontend/fonts/utheem.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2f244dcb5448440c6ad0e56d39125074b14fd285 GIT binary patch literal 70292 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 z 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 MU*@kIuH?u61fWggQ~&?~ literal 0 HcmV?d00001 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...
+ + +
+ + + + 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; + + var authorName = authorData["name"].ToString(); + var authorNid = authorData["nid"].ToString(); + + // Parse start date (format: dd-MM-yyyy) + var startDate = DateOnly.ParseExact(startDateStr, "dd-MM-yyyy", CultureInfo.InvariantCulture); + + // Parse petition bodies from markdown + var (petitionBodyDhiv, petitionBodyEng) = ParsePetitionBodies(body); + + // Check if petition already exists + var existingPetition = await _petitionRepository.FindByIdAsync(petitionId); + if (existingPetition != null) + { + return Conflict(new { message = $"A petition with ID '{petitionId}' already exists in the database" }); + } + + // Create or get author + var author = await _authorRepository.FindOneAsync(x => x.NID == authorNid); + if (author == null) + { + author = new Author + { + Id = Guid.NewGuid(), + Name = authorName, + NID = authorNid + }; + await _authorRepository.InsertOneAsync(author); + } + + // Create petition + var petition = new PetitionDetail + { + Id = petitionId, + StartDate = startDate, + NameDhiv = nameDhiv, + NameEng = nameEng, + AuthorId = author.Id, + PetitionBodyDhiv = petitionBodyDhiv, + PetitionBodyEng = petitionBodyEng, + SignatureCount = 0 + }; + + await _petitionRepository.InsertOneAsync(petition); + + // Save file with GUID prefix + Directory.CreateDirectory("Petitions"); + var newFileName = $"{Guid.NewGuid()}_{file.FileName}"; + var filePath = Path.Combine("Petitions", newFileName); + + await System.IO.File.WriteAllTextAsync(filePath, fileContent); + + return Ok(new + { + message = "Petition created successfully", + petitionId = petitionId, + fileName = newFileName, + filePath = filePath, + authorId = author.Id + }); + } + catch (Exception e) + { + return Problem(e.Message); + } + } + + private (string frontmatter, string body) ParseMarkdownFile(string content) + { + var lines = content.Split('\n'); + if (lines.Length < 3 || lines[0].Trim() != "---") + { + return (null, null); + } + + var frontmatterLines = new List(); + var bodyLines = new List(); + var inFrontmatter = true; + var frontmatterClosed = false; + + for (int i = 1; i < lines.Length; i++) + { + if (lines[i].Trim() == "---" && inFrontmatter) + { + inFrontmatter = false; + frontmatterClosed = true; + continue; + } + + if (inFrontmatter) + { + frontmatterLines.Add(lines[i]); + } + else + { + bodyLines.Add(lines[i]); + } + } + + if (!frontmatterClosed) + { + return (null, null); + } + + return (string.Join("\n", frontmatterLines), string.Join("\n", bodyLines)); + } + + private (string dhivehiBody, string englishBody) ParsePetitionBodies(string body) + { + var dhivehiBody = ""; + var englishBody = ""; + + var sections = body.Split("##", StringSplitOptions.RemoveEmptyEntries); + + foreach (var section in sections) + { + var trimmed = section.Trim(); + if (trimmed.StartsWith("Petition Body (Dhivehi)", StringComparison.OrdinalIgnoreCase)) + { + dhivehiBody = trimmed.Replace("Petition Body (Dhivehi)", "").Trim(); + } + else if (trimmed.StartsWith("Petition Body (English)", StringComparison.OrdinalIgnoreCase)) + { + englishBody = trimmed.Replace("Petition Body (English)", "").Trim(); + } + } + + return (dhivehiBody, englishBody); + } + } +} diff --git a/Submission.Api/Controllers/SignController.cs b/Submission.Api/Controllers/SignController.cs index f152f8c..3ce765e 100644 --- a/Submission.Api/Controllers/SignController.cs +++ b/Submission.Api/Controllers/SignController.cs @@ -1,10 +1,8 @@ -using System.CodeDom; -using System.Runtime.InteropServices; using Ashi.MongoInterface.Service; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; using Microsoft.Extensions.Caching.Memory; +using MongoDB.Driver; using Submission.Api.Dto; using Submission.Api.Models; @@ -14,16 +12,16 @@ namespace Submission.Api.Controllers [ApiController] public class SignController : ControllerBase { - + private readonly IMongoRepository _authorRepository; private readonly IMongoRepository _detailRepository; - private readonly IMongoRepository _signatureRepository; + private readonly IMongoRepository _signatureRepository; private readonly IMemoryCache _cache; public SignController( IMongoRepository authorRepository, IMongoRepository detailRepository, - IMongoRepository signatureRepository, + IMongoRepository signatureRepository, IMemoryCache cache) { _authorRepository = authorRepository; @@ -32,32 +30,53 @@ namespace Submission.Api.Controllers _cache = cache; } - [HttpPost(Name = "petition/{id}")] + [HttpPost("petition/{petition_id}", Name = "SignPetition")] [EnableRateLimiting("SignPetitionPolicy")] - public async Task SignDisHoe([FromRoute]Guid petition_id,[FromBody] WidgetsDto body) + public async Task SignDisHoe([FromRoute] Guid petition_id, [FromBody] WidgetsDto body) { + var cacheKey = $"petition_{petition_id}"; + + var pet = await _detailRepository.FindByIdAsync(petition_id); + + if (pet == null) + return NotFound(); + + //TODO : add svg validation + + //check to see if the same person signed the petition already //if dupe send error saying user already signed - var dupe = await _signatureRepository.FindOneAsync(x => x.IdCard == body.IdCard); - if (dupe != null) - return Problem("You already signed this petition"); - + var dupe = await _signatureRepository.FindOneAsync(x => x.IdCard == body.IdCard); + if (dupe != null) + return Problem("You already signed this petition"); + //add signature to the db - await _signatureRepository.InsertOneAsync(new Widget + await _signatureRepository.InsertOneAsync(new Signature { IdCard = body.IdCard, Name = body.Name, Signature_SVG = body.Signature, - Timestamp = DateTime.Now + Timestamp = DateTime.Now, + PetitionId = petition_id }); - + //update signature count - + if (pet.SignatureCount == null) + { + pet.SignatureCount = 0; + } + + var count_update_filter = Builders.Filter.Eq("_id", petition_id); + var Countupdate = Builders.Update.Inc("SignatureCount", 1); + await _detailRepository.UpdateOneAsync(count_update_filter, Countupdate); + + _cache.Remove(cacheKey); + return Ok("your signature has been submitted"); } - [HttpGet(Name = "petition/{id}")] - public async Task GetDisHoe([FromRoute] Guid petition_id) + [HttpGet("petition/{petition_id}", Name = "GetPetition")] + public async Task GetDisHoe([FromRoute] Guid petition_id) { var cacheKey = $"petition_{petition_id}"; @@ -68,7 +87,7 @@ namespace Submission.Api.Controllers } // Not in cache, fetch from database - var pet = await _detailRepository.FindByIdAsync(petition_id); + var pet = await _detailRepository.FindByIdAsync(petition_id); if (pet == null) return NotFound(); @@ -88,12 +107,14 @@ namespace Submission.Api.Controllers { Name = author.Name, NID = author.NID, - } + }, + + SignatureCount = pet.SignatureCount }; // Store in cache with 5 minute expiration var cacheOptions = new MemoryCacheEntryOptions() - .SetAbsoluteExpiration(TimeSpan.FromHours(12)); + .SetAbsoluteExpiration(TimeSpan.FromHours(1)); _cache.Set(cacheKey, dto, cacheOptions); diff --git a/Submission.Api/Models/Widget.cs b/Submission.Api/Models/Signature.cs similarity index 78% rename from Submission.Api/Models/Widget.cs rename to Submission.Api/Models/Signature.cs index d7803bc..1e81a71 100644 --- a/Submission.Api/Models/Widget.cs +++ b/Submission.Api/Models/Signature.cs @@ -3,10 +3,12 @@ namespace Submission.Api.Models; [BsonCollection("signatures")] -public class Widget : Document +public class Signature : Document { public string Name { get; set; } public string IdCard { get; set; } public string Signature_SVG { get; set; } public DateTime Timestamp { get; set; } + + public Guid PetitionId { get; set; } } \ No newline at end of file diff --git a/Submission.Api/Program.cs b/Submission.Api/Program.cs index baf5e08..6bf1f98 100644 --- a/Submission.Api/Program.cs +++ b/Submission.Api/Program.cs @@ -1,13 +1,14 @@ -using System.Configuration; using Ashi.MongoInterface; using Ashi.MongoInterface.Service; using Microsoft.AspNetCore.RateLimiting; using Microsoft.Extensions.Options; +using Submission.Api.Configuration; -var builder = WebApplication.CreateBuilder(args); +var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.Configure(builder.Configuration.GetSection("MongoDbSettings")); +builder.Services.Configure(builder.Configuration.GetSection("PetitionSettings")); builder.Services.AddSingleton(serviceProvider => serviceProvider.GetRequiredService>().Value); @@ -17,8 +18,9 @@ builder.Services.AddScoped((typeof(IMongoRepository<>)), typeof(MongoRepository< builder.Services.AddMemoryCache(); builder.Services.AddControllers(); -// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi -builder.Services.AddOpenApi(); +// Add Swagger/OpenAPI +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); // Add rate limiting builder.Services.AddRateLimiter(options => @@ -37,7 +39,8 @@ var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { - app.MapOpenApi(); + app.UseSwagger(); + app.UseSwaggerUI(); } app.UseHttpsRedirection(); diff --git a/Submission.Api/Properties/launchSettings.json b/Submission.Api/Properties/launchSettings.json index 26e3ab5..4fb9e15 100644 --- a/Submission.Api/Properties/launchSettings.json +++ b/Submission.Api/Properties/launchSettings.json @@ -4,7 +4,7 @@ "http": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": false, + "launchBrowser": true, "applicationUrl": "http://localhost:5299", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/Submission.Api/Submission.Api.csproj b/Submission.Api/Submission.Api.csproj index cb00eb4..7c6d60a 100644 --- a/Submission.Api/Submission.Api.csproj +++ b/Submission.Api/Submission.Api.csproj @@ -8,8 +8,9 @@ - + + diff --git a/Submission.Api/Submission.Api.csproj.user b/Submission.Api/Submission.Api.csproj.user index 9ff5820..e5a2ec0 100644 --- a/Submission.Api/Submission.Api.csproj.user +++ b/Submission.Api/Submission.Api.csproj.user @@ -1,6 +1,11 @@  - https + http + ApiControllerEmptyScaffolder + root/Common/Api + + + ProjectDebugger \ No newline at end of file diff --git a/Submission.Api/appsettings.json b/Submission.Api/appsettings.json index 10f68b8..ede0085 100644 --- a/Submission.Api/appsettings.json +++ b/Submission.Api/appsettings.json @@ -1,4 +1,11 @@ { + "PetitionSettings": { + "AllowPetitionCreation": true + }, + "MongoDbSettings": { + "ConnectionString": "mongodb://localhost:27017", + "DatabaseName": "petition_database" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/sample.Petition.md b/sample.Petition.md new file mode 100644 index 0000000..82b96f1 --- /dev/null +++ b/sample.Petition.md @@ -0,0 +1,17 @@ +--- +startDate: 14-12-2025 +nameDhiv: "މިސާލު ނަން" +nameEng: "Sample Petition Name" +author: + name: "Fishie" + nid: "AAAAA12345" +--- + +## Petition Body (Dhivehi) + +މިއީ ދިވެހި ބަސްނުވަތައް ލިޔެވިފައިވާ ބައިތައް. + +## Petition Body (English) + +This is the English version of the petition body written in clear paragraphs. +You can use normal Markdown formatting here such as **bold**, lists, and links. \ No newline at end of file