Compare commits
No commits in common. "main" and "readme-clarification" have entirely different histories.
main
...
readme-cla
|
@ -14,7 +14,3 @@ Cargo.lock
|
||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
*.pdb
|
*.pdb
|
||||||
|
|
||||||
|
|
||||||
.vscode
|
|
||||||
|
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
35
Cargo.toml
35
Cargo.toml
|
@ -6,9 +6,15 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# for web server
|
||||||
|
rocket = { version = "0.5.0", features = ["json", "uuid"] }
|
||||||
|
rocket_dyn_templates = { version = "0.1.0", features = ["handlebars", "tera"] }
|
||||||
|
# for json serialization
|
||||||
|
serde = "1.0.196"
|
||||||
# for random number generation
|
# for random number generation
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
# for json serialization
|
||||||
|
serde_json = "1.0.113"
|
||||||
# for uuid generation
|
# for uuid generation
|
||||||
uuid = { version = "1.7.0", features = ["serde", "v4"] }
|
uuid = { version = "1.7.0", features = ["serde", "v4"] }
|
||||||
# for command line arguments
|
# for command line arguments
|
||||||
|
@ -17,30 +23,5 @@ structopt = "0.3.23"
|
||||||
csv = "1.1.6"
|
csv = "1.1.6"
|
||||||
clap = { version = "4.3.24", features = ["derive"] }
|
clap = { version = "4.3.24", features = ["derive"] }
|
||||||
|
|
||||||
|
|
||||||
stv-rs = "0.3.0"
|
stv-rs = "0.3.0"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde_derive = "1.0"
|
|
||||||
serde_yaml = "0.9.28"
|
|
||||||
serde_json = "1.0"
|
|
||||||
base64 = "~0.22.1"
|
|
||||||
futures = "0.3.30"
|
|
||||||
hyper = "1.3.1"
|
|
||||||
url = "2.5.0"
|
|
||||||
|
|
||||||
oauth2 = "4.4.2"
|
|
||||||
reqwest = { version = "0.12", features = ["blocking", "json"] }
|
|
||||||
jsonwebtoken = "9.3.0"
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
|
||||||
sqlx = { version = "0.7", features = ["sqlite", "runtime-tokio-native-tls"] }
|
|
||||||
anyhow = "1.0"
|
|
||||||
bcrypt = "0.15.1"
|
|
||||||
|
|
||||||
itertools = "0.13"
|
|
||||||
indoc = "1.0.3"
|
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
tokio-core = "*"
|
|
||||||
|
|
15
README.md
15
README.md
|
@ -1,22 +1,11 @@
|
||||||
# Vote-rs
|
# Vote-rs
|
||||||
|
|
||||||
Currently in development....
|
Currently in development
|
||||||
|
|
||||||
## current functionality
|
|
||||||
|
|
||||||
Converts a csv file into a `.blt`file [(OpenSTV/OpaVote ballots)](https://www.opavote.com/help/overview#blt-file-format).
|
Converts a csv file into a `.blt`file [(OpenSTV/OpaVote ballots)](https://www.opavote.com/help/overview#blt-file-format).
|
||||||
|
|
||||||
stared on mocups and api documentation for a possible api to the application.
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
|
|
||||||
Create a webapplication to commit, some simple elections.
|
|
||||||
|
|
||||||
as there already seem to exist multiple diffrent voting result libraries/application in rust,
|
|
||||||
the aim of this is rather to create the webui for participants to submit their votes, and use a library to get the results after a election is done.
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo -Z unstable-options build --out-dir ./build
|
cargo +nightly -Z unstable-options build --out-dir ./build
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
# Authorization
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**to_date** | **String** | | [optional] [default to null]
|
|
||||||
**from_date** | **String** | | [optional] [default to null]
|
|
||||||
**user** | **String** | | [optional] [default to null]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
# Credentials
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**username** | **String** | | [optional] [default to null]
|
|
||||||
**password** | **String** | | [optional] [default to null]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
|
@ -1,174 +0,0 @@
|
||||||
# \DefaultApi
|
|
||||||
|
|
||||||
All URIs are relative to *https://localhost/api*
|
|
||||||
|
|
||||||
Method | HTTP request | Description
|
|
||||||
------------- | ------------- | -------------
|
|
||||||
[**auth_login_post**](DefaultApi.md#auth_login_post) | **Post** /auth/login | Authenticate user
|
|
||||||
[**auth_token_post**](DefaultApi.md#auth_token_post) | **Post** /auth/token | Generate authentication token for another user
|
|
||||||
[**elections_all_get**](DefaultApi.md#elections_all_get) | **Get** /elections/all | Get all existing elections
|
|
||||||
[**elections_create_post**](DefaultApi.md#elections_create_post) | **Post** /elections/create | Create new election
|
|
||||||
[**elections_id_get**](DefaultApi.md#elections_id_get) | **Get** /elections/{id} | Get all existing elections
|
|
||||||
[**elections_id_post**](DefaultApi.md#elections_id_post) | **Post** /elections/{id} | Vote in exsisting election
|
|
||||||
|
|
||||||
|
|
||||||
# **auth_login_post**
|
|
||||||
> ::models::InlineResponse200 auth_login_post(ctx, credentials)
|
|
||||||
Authenticate user
|
|
||||||
|
|
||||||
### Required Parameters
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------- | ------------- | ------------- | -------------
|
|
||||||
**ctx** | **context.Context** | context containing the authentication | nil if no authentication
|
|
||||||
**credentials** | [**Credentials**](Credentials.md)| |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**::models::InlineResponse200**](inline_response_200.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[JWT](../README.md#JWT)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
# **auth_token_post**
|
|
||||||
> ::models::InlineResponse2001 auth_token_post(ctx, authorization, token)
|
|
||||||
Generate authentication token for another user
|
|
||||||
|
|
||||||
### Required Parameters
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------- | ------------- | ------------- | -------------
|
|
||||||
**ctx** | **context.Context** | context containing the authentication | nil if no authentication
|
|
||||||
**authorization** | **String**| Your authorization token |
|
|
||||||
**token** | [**Authorization**](Authorization.md)| |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**::models::InlineResponse2001**](inline_response_200_1.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[JWT](../README.md#JWT)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
# **elections_all_get**
|
|
||||||
> ::models::ElectionList elections_all_get(ctx, authorization)
|
|
||||||
Get all existing elections
|
|
||||||
|
|
||||||
### Required Parameters
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------- | ------------- | ------------- | -------------
|
|
||||||
**ctx** | **context.Context** | context containing the authentication | nil if no authentication
|
|
||||||
**authorization** | **String**| Your authorization token |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**::models::ElectionList**](ElectionList.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[JWT](../README.md#JWT)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
# **elections_create_post**
|
|
||||||
> ::models::Election elections_create_post(ctx, authorization, election)
|
|
||||||
Create new election
|
|
||||||
|
|
||||||
### Required Parameters
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------- | ------------- | ------------- | -------------
|
|
||||||
**ctx** | **context.Context** | context containing the authentication | nil if no authentication
|
|
||||||
**authorization** | **String**| Your authorization token |
|
|
||||||
**election** | [**Election**](Election.md)| |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**::models::Election**](Election.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[JWT](../README.md#JWT)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
# **elections_id_get**
|
|
||||||
> ::models::Election elections_id_get(ctx, authorization, id)
|
|
||||||
Get all existing elections
|
|
||||||
|
|
||||||
### Required Parameters
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------- | ------------- | ------------- | -------------
|
|
||||||
**ctx** | **context.Context** | context containing the authentication | nil if no authentication
|
|
||||||
**authorization** | **String**| Your authorization token |
|
|
||||||
**id** | **String**| |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**::models::Election**](Election.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[JWT](../README.md#JWT)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
# **elections_id_post**
|
|
||||||
> ::models::Vote elections_id_post(ctx, authorization, election)
|
|
||||||
Vote in exsisting election
|
|
||||||
|
|
||||||
### Required Parameters
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------- | ------------- | ------------- | -------------
|
|
||||||
**ctx** | **context.Context** | context containing the authentication | nil if no authentication
|
|
||||||
**authorization** | **String**| Your authorization token |
|
|
||||||
**election** | [**Vote**](Vote.md)| |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**::models::Vote**](Vote.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[JWT](../README.md#JWT)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
# Election
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | | [optional] [default to null]
|
|
||||||
**username** | **String** | | [optional] [default to null]
|
|
||||||
**name** | **String** | | [optional] [default to null]
|
|
||||||
**description** | **String** | | [optional] [default to null]
|
|
||||||
**start_date** | **String** | | [optional] [default to null]
|
|
||||||
**end_date** | **String** | | [optional] [default to null]
|
|
||||||
**items** | [**Vec<::models::ElectionItem>**](ElectionItem.md) | | [optional] [default to null]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
# ElectionItem
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | | [optional] [default to null]
|
|
||||||
**name** | **String** | | [optional] [default to null]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
# ElectionList
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
# InlineResponse200
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**token** | **String** | | [optional] [default to null]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
# InlineResponse2001
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**token** | **String** | | [optional] [default to null]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
12
docs/User.md
12
docs/User.md
|
@ -1,12 +0,0 @@
|
||||||
# User
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | | [optional] [default to null]
|
|
||||||
**username** | **String** | | [optional] [default to null]
|
|
||||||
**password** | **String** | | [optional] [default to null]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
12
docs/Vote.md
12
docs/Vote.md
|
@ -1,12 +0,0 @@
|
||||||
# Vote
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**authorization** | [***::models::Authorization**](Authorization.md) | | [optional] [default to null]
|
|
||||||
**userid** | **String** | | [optional] [default to null]
|
|
||||||
**data** | [**Vec<::models::VoteItem>**](VoteItem.md) | | [optional] [default to null]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
# VoteItem
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**item** | [***::models::ElectionItem**](ElectionItem.md) | | [optional] [default to null]
|
|
||||||
**value** | **f32** | | [optional] [default to null]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
66
flake.lock
66
flake.lock
|
@ -1,66 +0,0 @@
|
||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"fenix": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"rust-analyzer-src": "rust-analyzer-src"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1716618425,
|
|
||||||
"narHash": "sha256-eZs7f4izo6t0AmOI1IAU6/ZbbXrxMPGdo+khe4hP3Rk=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "fenix",
|
|
||||||
"rev": "9a9fafd0c3f796b675acb2e16ae238d4fd2cbdb5",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "fenix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1716509168,
|
|
||||||
"narHash": "sha256-4zSIhSRRIoEBwjbPm3YiGtbd8HDWzFxJjw5DYSDy1n8=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "bfb7a882678e518398ce9a31a881538679f6f092",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"fenix": "fenix",
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rust-analyzer-src": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1716572615,
|
|
||||||
"narHash": "sha256-mVUbarr4PNjERDk+uaoitPq7eL7De0ythZehezAzug8=",
|
|
||||||
"owner": "rust-lang",
|
|
||||||
"repo": "rust-analyzer",
|
|
||||||
"rev": "a55e8bf09cdfc25066b77823cc98976a51af8a8b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "rust-lang",
|
|
||||||
"ref": "nightly",
|
|
||||||
"repo": "rust-analyzer",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
39
flake.nix
39
flake.nix
|
@ -1,37 +1,28 @@
|
||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
devenv.url = "github:cachix/devenv";
|
||||||
|
|
||||||
fenix.url = "github:nix-community/fenix";
|
fenix.url = "github:nix-community/fenix";
|
||||||
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, devenv, ... }@inputs:
|
||||||
outputs = { self, nixpkgs, fenix }@inputs:
|
|
||||||
let
|
let
|
||||||
systems = [
|
|
||||||
"x86_64-linux"
|
|
||||||
"aarch64-linux"
|
|
||||||
"aarch64-darwin"
|
|
||||||
];
|
|
||||||
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: let
|
|
||||||
toolchain = fenix.packages.${system}.complete;
|
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
system = "x86_64-linux";
|
||||||
overlays = [
|
|
||||||
(_: super: let pkgs = fenix.inputs.nixpkgs.legacyPackages.${system}; in fenix.overlays.default pkgs pkgs)
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
in f system pkgs toolchain);
|
|
||||||
in {
|
in {
|
||||||
devShell = forAllSystems (system: pkgs: toolchain: pkgs.mkShell {
|
devShell.x86_64-linux = devenv.lib.mkShell {
|
||||||
packages = [
|
inherit inputs pkgs;
|
||||||
(toolchain.withComponents [
|
modules = [
|
||||||
"cargo" "rustc" "rustfmt" "clippy"
|
{
|
||||||
])
|
languages.rust = {
|
||||||
pkgs.openssl
|
enable = true;
|
||||||
pkgs.pkg-config
|
channel = "stable";
|
||||||
];
|
};
|
||||||
RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/library";
|
}
|
||||||
});
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
303
openapi.yaml
303
openapi.yaml
|
@ -1,303 +0,0 @@
|
||||||
swagger: "2.0"
|
|
||||||
info:
|
|
||||||
version: "0.0.1"
|
|
||||||
title: vote-rs API
|
|
||||||
description: API for conducting electronic voting
|
|
||||||
basePath: /api
|
|
||||||
schemes:
|
|
||||||
- https
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
securityDefinitions:
|
|
||||||
JWT:
|
|
||||||
type: apiKey
|
|
||||||
name: Authorization
|
|
||||||
in: header
|
|
||||||
security:
|
|
||||||
- JWT: []
|
|
||||||
|
|
||||||
definitions:
|
|
||||||
|
|
||||||
User:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
username:
|
|
||||||
type: string
|
|
||||||
password:
|
|
||||||
type: string
|
|
||||||
|
|
||||||
Authorization:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
username:
|
|
||||||
type: string
|
|
||||||
namespace:
|
|
||||||
type: string
|
|
||||||
|
|
||||||
AuthorizationItems:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/definitions/Authorization"
|
|
||||||
|
|
||||||
|
|
||||||
Election:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
username:
|
|
||||||
type: string
|
|
||||||
namespace:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
start_date:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
end_date:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
items:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/definitions/ElectionItem"
|
|
||||||
|
|
||||||
ElectionItem:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
|
|
||||||
ElectionList:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/definitions/Election"
|
|
||||||
|
|
||||||
|
|
||||||
VoteItem:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
item:
|
|
||||||
$ref: "#/definitions/ElectionItem"
|
|
||||||
value:
|
|
||||||
type: number
|
|
||||||
|
|
||||||
Vote:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
userid:
|
|
||||||
type: string
|
|
||||||
data:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/definitions/VoteItem"
|
|
||||||
|
|
||||||
paths:
|
|
||||||
/auth/login:
|
|
||||||
post:
|
|
||||||
summary: Authenticate user
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
parameters:
|
|
||||||
- in: body
|
|
||||||
name: credentials
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
username:
|
|
||||||
type: string
|
|
||||||
password:
|
|
||||||
type: string
|
|
||||||
format: password
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: Login successful
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
token:
|
|
||||||
type: string
|
|
||||||
401:
|
|
||||||
description: Unauthorized
|
|
||||||
|
|
||||||
/auth/authorization:
|
|
||||||
post:
|
|
||||||
summary: Generate authentication token for another user
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
parameters:
|
|
||||||
- in: header
|
|
||||||
name: Authorization
|
|
||||||
description: Your authorization token
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
format: JWT
|
|
||||||
|
|
||||||
- in: body
|
|
||||||
name: authorization
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Authorization"
|
|
||||||
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: Token generated successfully
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
token:
|
|
||||||
type: string
|
|
||||||
format: JWT
|
|
||||||
401:
|
|
||||||
description: Unauthorized
|
|
||||||
get:
|
|
||||||
summary: Generate authentication token for another user
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
parameters:
|
|
||||||
- in: header
|
|
||||||
name: Authorization
|
|
||||||
description: Your authorization token
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
format: JWT
|
|
||||||
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: List of authorizations you have gotten.
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/AuthorizationItems"
|
|
||||||
401:
|
|
||||||
description: Unauthorized
|
|
||||||
|
|
||||||
delete:
|
|
||||||
summary: Delete all Authorization you have given to a user
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
parameters:
|
|
||||||
- in: header
|
|
||||||
name: Authorization
|
|
||||||
description: Your authorization token
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
format: JWT
|
|
||||||
- in: body
|
|
||||||
name: Authorisation
|
|
||||||
description: The authorization token you want to delete
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Authorization"
|
|
||||||
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: Sucsess
|
|
||||||
401:
|
|
||||||
description: Unauthorized
|
|
||||||
|
|
||||||
|
|
||||||
/elections:
|
|
||||||
post:
|
|
||||||
summary: Create new election
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
parameters:
|
|
||||||
- in: header
|
|
||||||
name: Authorization
|
|
||||||
description: Your authorization token
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
format: JWT
|
|
||||||
|
|
||||||
- in: body
|
|
||||||
name: election
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Election"
|
|
||||||
responses:
|
|
||||||
201:
|
|
||||||
description: Election created successfully
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Election"
|
|
||||||
401:
|
|
||||||
description: Unauthorized
|
|
||||||
|
|
||||||
get:
|
|
||||||
summary: Get all existing elections
|
|
||||||
parameters:
|
|
||||||
- in: header
|
|
||||||
name: Authorization
|
|
||||||
description: Your authorization token
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
format: JWT
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: List of all existing elections
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/ElectionList"
|
|
||||||
401:
|
|
||||||
description: Unauthorized
|
|
||||||
|
|
||||||
/elections/{id}:
|
|
||||||
get:
|
|
||||||
summary: Get all existing elections
|
|
||||||
parameters:
|
|
||||||
- in: header
|
|
||||||
name: Authorization
|
|
||||||
description: Your authorization token
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
format: JWT
|
|
||||||
- in: path
|
|
||||||
name: id
|
|
||||||
type: string
|
|
||||||
required: true
|
|
||||||
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: List of all existing elections
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Election"
|
|
||||||
401:
|
|
||||||
description: Unauthorized
|
|
||||||
|
|
||||||
post:
|
|
||||||
summary: Vote in exsisting election
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
parameters:
|
|
||||||
- in: header
|
|
||||||
name: Authorization
|
|
||||||
description: Your authorization token
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
format: JWT
|
|
||||||
|
|
||||||
- in: body
|
|
||||||
name: election
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Vote"
|
|
||||||
|
|
||||||
responses:
|
|
||||||
201:
|
|
||||||
description: Election created successfully
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/Vote"
|
|
||||||
401:
|
|
||||||
description: Unauthorized
|
|
||||||
|
|
118
src/api.rs
118
src/api.rs
|
@ -1,118 +0,0 @@
|
||||||
#![feature(proc_macro_hygiene, decl_macro)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
|
||||||
#[macro_use] extern crate rocket_contrib;
|
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
|
|
||||||
use rocket::http::Status;
|
|
||||||
use rocket_contrib::json::{Json, JsonValue};
|
|
||||||
|
|
||||||
use crate::auth::login;
|
|
||||||
|
|
||||||
|
|
||||||
// Define data models
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct User {
|
|
||||||
id: String,
|
|
||||||
username: String,
|
|
||||||
password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct Authorization {
|
|
||||||
to_date: String,
|
|
||||||
from_date: String,
|
|
||||||
user: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct Election {
|
|
||||||
id: String,
|
|
||||||
username: String,
|
|
||||||
name: String,
|
|
||||||
description: String,
|
|
||||||
start_date: String,
|
|
||||||
end_date: String,
|
|
||||||
items: Vec<ElectionItem>,
|
|
||||||
}
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct ElectionItem {
|
|
||||||
id: String,
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct VoteItem {
|
|
||||||
item: ElectionItem,
|
|
||||||
value: f64,
|
|
||||||
}
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct Vote {
|
|
||||||
authorization: Authorization,
|
|
||||||
userid: String,
|
|
||||||
data: Vec<VoteItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/auth/login", format = "application/json", data = "<credentials>")]
|
|
||||||
async fn handle_login(credentials: Json<User>, db: Db) -> JsonValue {
|
|
||||||
match login(credentials.email, credentials.password, db).await {
|
|
||||||
Ok(token) => json!({
|
|
||||||
"token": token
|
|
||||||
}),
|
|
||||||
Err(error) => json!({
|
|
||||||
"error": error
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[post("/auth/token", format = "application/json", data = "<token>")]
|
|
||||||
fn generate_token(token: Json<Authorization>) -> JsonValue {
|
|
||||||
// Token generation logic here
|
|
||||||
json!({
|
|
||||||
"token": "generated_token"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/elections/create", format = "application/json", data = "<election>")]
|
|
||||||
fn create_election(election: Json<Election>) -> Result<JsonValue, Status> {
|
|
||||||
// Election creation logic here
|
|
||||||
Ok(json!(election))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/elections/all")]
|
|
||||||
fn get_all_elections() -> JsonValue {
|
|
||||||
// Retrieve all elections logic here
|
|
||||||
json!([
|
|
||||||
// List of all existing elections
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/elections/<id>")]
|
|
||||||
fn get_election(id: String) -> JsonValue {
|
|
||||||
// Retrieve single election logic here
|
|
||||||
json!({
|
|
||||||
"id": id,
|
|
||||||
// Other election details
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/elections/<id>", format = "application/json", data = "<vote>")]
|
|
||||||
fn vote_in_election(id: String, vote: Json<Vote>) -> Result<JsonValue, Status> {
|
|
||||||
// Voting logic here
|
|
||||||
Ok(json!(vote))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rocket fairings to set up CORS and other middlewares can be added here
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
rocket::ignite()
|
|
||||||
.mount("/api", routes![
|
|
||||||
login,
|
|
||||||
generate_token,
|
|
||||||
create_election,
|
|
||||||
get_all_elections,
|
|
||||||
get_election,
|
|
||||||
vote_in_election,
|
|
||||||
])
|
|
||||||
.launch();
|
|
||||||
}
|
|
33
src/auth.rs
33
src/auth.rs
|
@ -1,33 +0,0 @@
|
||||||
use jsonwebtoken::{encode, Header, EncodingKey};
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use chrono::{Utc, Duration};
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct Claims {
|
|
||||||
sub: String,
|
|
||||||
exp: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn login(username: &str, _password: &str, _db: &sqlx::SqlitePool) -> Result<String, Box<dyn Error>> {
|
|
||||||
// Normally, you would validate the username and password against the database here.
|
|
||||||
// For the mock, we just generate a token for any username.
|
|
||||||
|
|
||||||
let expiration = Utc::now()
|
|
||||||
.checked_add_signed(Duration::minutes(60))
|
|
||||||
.expect("valid timestamp")
|
|
||||||
.timestamp() as usize;
|
|
||||||
|
|
||||||
let claims = Claims {
|
|
||||||
sub: username.to_owned(),
|
|
||||||
exp: expiration,
|
|
||||||
};
|
|
||||||
|
|
||||||
let token = encode(
|
|
||||||
&Header::default(),
|
|
||||||
&claims,
|
|
||||||
&EncodingKey::from_secret("secret".as_ref()),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(token)
|
|
||||||
}
|
|
239
src/db.rs
239
src/db.rs
|
@ -1,239 +0,0 @@
|
||||||
use sqlx::{sqlite::SqlitePool, Pool, Row};
|
|
||||||
use std::error::Error;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use indoc::indoc;
|
|
||||||
|
|
||||||
pub struct Connection {
|
|
||||||
pool: Pool<sqlx::Sqlite>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect() -> Result<Connection, Box<dyn Error>> {
|
|
||||||
let database_url = "sqlite::memory:"; // Use an in-memory SQLite database for testing
|
|
||||||
let pool = SqlitePool::connect(database_url).await?;
|
|
||||||
Ok(Connection { pool })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn init_db(conn: &Connection) -> Result<(), Box<dyn Error>> {
|
|
||||||
sqlx::query(indoc! {"
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
username TEXT NOT NULL UNIQUE,
|
|
||||||
jwt_token TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS elections (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
username TEXT NOT NULL,
|
|
||||||
namespace TEXT NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
start_date TEXT NOT NULL,
|
|
||||||
end_date TEXT NOT NULL,
|
|
||||||
FOREIGN KEY (username) REFERENCES users(username)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS authorizations (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
from_date TEXT NOT NULL,
|
|
||||||
to_date TEXT NOT NULL,
|
|
||||||
from_user TEXT NOT NULL,
|
|
||||||
to_user TEXT NOT NULL,
|
|
||||||
namespace TEXT NOT NULL,
|
|
||||||
FOREIGN KEY (from_user) REFERENCES users(username),
|
|
||||||
FOREIGN KEY (to_user) REFERENCES users(username)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS votes (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
value INTEGER NOT NULL,
|
|
||||||
option_id INTEGER NOT NULL,
|
|
||||||
election_id INTEGER NOT NULL,
|
|
||||||
option_name TEXT NOT NULL,
|
|
||||||
user TEXT NOT NULL,
|
|
||||||
namespace TEXT NOT NULL,
|
|
||||||
date TEXT NOT NULL,
|
|
||||||
FOREIGN KEY (election_id) REFERENCES elections(id),
|
|
||||||
FOREIGN KEY (user) REFERENCES users(username)
|
|
||||||
);
|
|
||||||
"})
|
|
||||||
.execute(&conn.pool)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Users
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct User {
|
|
||||||
pub id: i64,
|
|
||||||
pub username: String,
|
|
||||||
pub jwt_token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn insert_user(conn: &Connection, username: &str, jwt_token: &str) -> Result<(), Box<dyn Error>> {
|
|
||||||
sqlx::query("INSERT INTO users (username, jwt_token) VALUES (?, ?)")
|
|
||||||
.bind(username)
|
|
||||||
.bind(jwt_token)
|
|
||||||
.execute(&conn.pool)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_user(conn: &Connection, username: &str) -> Result<Option<User>, Box<dyn Error>> {
|
|
||||||
let row = sqlx::query("SELECT id, username, jwt_token FROM users WHERE username = ?")
|
|
||||||
.bind(username)
|
|
||||||
.fetch_optional(&conn.pool)
|
|
||||||
.await?;
|
|
||||||
if let Some(row) = row {
|
|
||||||
Ok(Some(User {
|
|
||||||
id: row.get("id"),
|
|
||||||
username: row.get("username"),
|
|
||||||
jwt_token: row.get("jwt_token"),
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Elections
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct Election {
|
|
||||||
pub id: i64,
|
|
||||||
pub name: String,
|
|
||||||
pub username: String,
|
|
||||||
pub namespace: String,
|
|
||||||
pub description: String,
|
|
||||||
pub start_date: String,
|
|
||||||
pub end_date: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn insert_election(conn: &Connection, election: &Election) -> Result<(), Box<dyn Error>> {
|
|
||||||
sqlx::query(indoc! {"
|
|
||||||
INSERT INTO elections (name, username, namespace, description, start_date, end_date)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
|
||||||
"})
|
|
||||||
.bind(&election.name)
|
|
||||||
.bind(&election.username)
|
|
||||||
.bind(&election.namespace)
|
|
||||||
.bind(&election.description)
|
|
||||||
.bind(&election.start_date)
|
|
||||||
.bind(&election.end_date)
|
|
||||||
.execute(&conn.pool)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_election(conn: &Connection, id: i64) -> Result<Option<Election>, Box<dyn Error>> {
|
|
||||||
let row = sqlx::query("SELECT id, name, username, namespace, description, start_date, end_date FROM elections WHERE id = ?")
|
|
||||||
.bind(id)
|
|
||||||
.fetch_optional(&conn.pool)
|
|
||||||
.await?;
|
|
||||||
if let Some(row) = row {
|
|
||||||
Ok(Some(Election {
|
|
||||||
id: row.get("id"),
|
|
||||||
name: row.get("name"),
|
|
||||||
username: row.get("username"),
|
|
||||||
namespace: row.get("namespace"),
|
|
||||||
description: row.get("description"),
|
|
||||||
start_date: row.get("start_date"),
|
|
||||||
end_date: row.get("end_date"),
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authorizations
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct Authorization {
|
|
||||||
pub id: i64,
|
|
||||||
pub from_date: String,
|
|
||||||
pub to_date: String,
|
|
||||||
pub from_user: String,
|
|
||||||
pub to_user: String,
|
|
||||||
pub namespace: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn insert_authorization(conn: &Connection, authorization: &Authorization) -> Result<(), Box<dyn Error>> {
|
|
||||||
sqlx::query(indoc! {"
|
|
||||||
INSERT INTO authorizations (from_date, to_date, from_user, to_user, namespace)
|
|
||||||
VALUES (?, ?, ?, ?, ?)
|
|
||||||
"})
|
|
||||||
.bind(&authorization.from_date)
|
|
||||||
.bind(&authorization.to_date)
|
|
||||||
.bind(&authorization.from_user)
|
|
||||||
.bind(&authorization.to_user)
|
|
||||||
.bind(&authorization.namespace)
|
|
||||||
.execute(&conn.pool)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_authorization(conn: &Connection, id: i64) -> Result<Option<Authorization>, Box<dyn Error>> {
|
|
||||||
let row = sqlx::query("SELECT id, from_date, to_date, from_user, to_user, namespace FROM authorizations WHERE id = ?")
|
|
||||||
.bind(id)
|
|
||||||
.fetch_optional(&conn.pool)
|
|
||||||
.await?;
|
|
||||||
if let Some(row) = row {
|
|
||||||
Ok(Some(Authorization {
|
|
||||||
id: row.get("id"),
|
|
||||||
from_date: row.get("from_date"),
|
|
||||||
to_date: row.get("to_date"),
|
|
||||||
from_user: row.get("from_user"),
|
|
||||||
to_user: row.get("to_user"),
|
|
||||||
namespace: row.get("namespace"),
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Votes
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct Vote {
|
|
||||||
pub id: i64,
|
|
||||||
pub value: i32,
|
|
||||||
pub option_id: i64,
|
|
||||||
pub election_id: i64,
|
|
||||||
pub option_name: String,
|
|
||||||
pub user: String,
|
|
||||||
pub namespace: String,
|
|
||||||
pub date: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn insert_vote(conn: &Connection, vote: &Vote) -> Result<(), Box<dyn Error>> {
|
|
||||||
sqlx::query(indoc! {"
|
|
||||||
INSERT INTO votes (value, option_id, election_id, option_name, user, namespace, date)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
||||||
"})
|
|
||||||
.bind(vote.value)
|
|
||||||
.bind(vote.option_id)
|
|
||||||
.bind(vote.election_id)
|
|
||||||
.bind(&vote.option_name)
|
|
||||||
.bind(&vote.user)
|
|
||||||
.bind(&vote.namespace)
|
|
||||||
.bind(&vote.date)
|
|
||||||
.execute(&conn.pool)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_vote(conn: &Connection, id: i64) -> Result<Option<Vote>, Box<dyn Error>> {
|
|
||||||
let row = sqlx::query("SELECT id, value, option_id, election_id, option_name, user, namespace, date FROM votes WHERE id = ?")
|
|
||||||
.bind(id)
|
|
||||||
.fetch_optional(&conn.pool)
|
|
||||||
.await?;
|
|
||||||
if let Some(row) = row {
|
|
||||||
Ok(Some(Vote {
|
|
||||||
id: row.get("id"),
|
|
||||||
value: row.get("value"),
|
|
||||||
option_id: row.get("option_id"),
|
|
||||||
election_id: row.get("election_id"),
|
|
||||||
option_name: row.get("option_name"),
|
|
||||||
user: row.get("user"),
|
|
||||||
namespace: row.get("namespace"),
|
|
||||||
date: row.get("date"),
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
219
src/main.rs
219
src/main.rs
|
@ -1,186 +1,61 @@
|
||||||
// main.rs
|
#[macro_use]
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
mod db;
|
use rocket::form::Form;
|
||||||
|
use rocket::response::Redirect;
|
||||||
|
use rocket::State; // Add this line
|
||||||
|
use rocket::fs::FileServer;
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use rocket_dyn_templates::Template;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use csv::Writer;
|
||||||
|
|
||||||
#[cfg(test)]
|
mod voting;
|
||||||
mod tests {
|
|
||||||
use super::db;
|
|
||||||
use tokio;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[derive(rocket::FromForm)]
|
||||||
async fn test_connection() {
|
struct Vote {
|
||||||
let result = db::connect().await;
|
option: String,
|
||||||
assert!(result.is_ok(), "Database connection failed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
type Votes = Mutex<Writer<std::fs::File>>;
|
||||||
async fn test_insert_user() {
|
|
||||||
let conn = db::connect().await.unwrap();
|
#[post("/vote", data = "<vote>")]
|
||||||
db::init_db(&conn).await.unwrap();
|
fn vote(vote: Form<Vote>, votes: &State<Votes>) -> Redirect {
|
||||||
let result = db::insert_user(&conn, "test_user", "test_jwt").await;
|
let mut writer = votes.lock().unwrap();
|
||||||
assert!(result.is_ok(), "Insert user operation failed");
|
writer.write_record(&[vote.option.clone()]).unwrap();
|
||||||
|
writer.flush().unwrap();
|
||||||
|
|
||||||
|
Redirect::to("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[post("/results")]
|
||||||
async fn test_get_user() {
|
fn results() -> Json<HashMap<String, String>> {
|
||||||
let conn = db::connect().await.unwrap();
|
let election_data = voting::csv_to_election_data("votes.csv", 1, "Election".to_string());
|
||||||
db::init_db(&conn).await.unwrap();
|
let blt = voting::election_data_to_blt(election_data);
|
||||||
db::insert_user(&conn, "test_user", "test_jwt").await.unwrap();
|
let results = voting::run_blt(blt);
|
||||||
let result = db::get_user(&conn, "test_user").await;
|
Json(results)
|
||||||
assert!(result.is_ok(), "Get user operation failed");
|
|
||||||
let user = result.unwrap();
|
|
||||||
assert!(user.is_some(), "User not found");
|
|
||||||
let user = user.unwrap();
|
|
||||||
assert_eq!(user.username, "test_user", "Username does not match");
|
|
||||||
assert_eq!(user.jwt_token, "test_jwt", "JWT token does not match");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[get("/")]
|
||||||
async fn test_insert_election() {
|
fn index() -> Template {
|
||||||
let conn = db::connect().await.unwrap();
|
let context: HashMap<&str, &str> = HashMap::new();
|
||||||
db::init_db(&conn).await.unwrap();
|
Template::render("index", &context)
|
||||||
db::insert_user(&conn, "test_user", "test_jwt").await.unwrap();
|
|
||||||
let election = db::Election {
|
|
||||||
id: 0,
|
|
||||||
name: "Election 1".to_string(),
|
|
||||||
username: "test_user".to_string(),
|
|
||||||
namespace: "namespace2".to_string(),
|
|
||||||
description: "Description 1".to_string(),
|
|
||||||
start_date: "2024-05-01T08:00".to_string(),
|
|
||||||
end_date: "2024-05-10T20:00".to_string(),
|
|
||||||
};
|
|
||||||
let result = db::insert_election(&conn, &election).await;
|
|
||||||
assert!(result.is_ok(), "Insert election operation failed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[launch]
|
||||||
async fn test_get_election() {
|
fn rocket() -> _ {
|
||||||
let conn = db::connect().await.unwrap();
|
let file = std::fs::OpenOptions::new()
|
||||||
db::init_db(&conn).await.unwrap();
|
.create(true)
|
||||||
db::insert_user(&conn, "test_user", "test_jwt").await.unwrap();
|
.append(true)
|
||||||
let election = db::Election {
|
.open("votes.csv")
|
||||||
id: 0,
|
.unwrap();
|
||||||
name: "Election 1".to_string(),
|
|
||||||
username: "test_user".to_string(),
|
|
||||||
namespace: "namespace2".to_string(),
|
|
||||||
description: "Description 1".to_string(),
|
|
||||||
start_date: "2024-05-01T08:00".to_string(),
|
|
||||||
end_date: "2024-05-10T20:00".to_string(),
|
|
||||||
};
|
|
||||||
db::insert_election(&conn, &election).await.unwrap();
|
|
||||||
let result = db::get_election(&conn, 1).await;
|
|
||||||
assert!(result.is_ok(), "Get election operation failed");
|
|
||||||
let election = result.unwrap();
|
|
||||||
assert!(election.is_some(), "Election not found");
|
|
||||||
let election = election.unwrap();
|
|
||||||
assert_eq!(election.name, "Election 1", "Election name does not match");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
let writer = csv::Writer::from_writer(file);
|
||||||
async fn test_insert_authorization() {
|
|
||||||
let conn = db::connect().await.unwrap();
|
|
||||||
db::init_db(&conn).await.unwrap();
|
|
||||||
db::insert_user(&conn, "user1", "jwt1").await.unwrap();
|
|
||||||
db::insert_user(&conn, "user2", "jwt2").await.unwrap();
|
|
||||||
let authorization = db::Authorization {
|
|
||||||
id: 0,
|
|
||||||
from_date: "2024-05-01T00:00".to_string(),
|
|
||||||
to_date: "2024-05-10T23:59".to_string(),
|
|
||||||
from_user: "user1".to_string(),
|
|
||||||
to_user: "user2".to_string(),
|
|
||||||
namespace: "namespace".to_string(),
|
|
||||||
};
|
|
||||||
let result = db::insert_authorization(&conn, &authorization).await;
|
|
||||||
assert!(result.is_ok(), "Insert authorization operation failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
rocket::build()
|
||||||
async fn test_get_authorization() {
|
.attach(Template::fairing())
|
||||||
let conn = db::connect().await.unwrap();
|
.manage(Mutex::new(writer))
|
||||||
db::init_db(&conn).await.unwrap();
|
.mount("/", routes![index, vote])
|
||||||
db::insert_user(&conn, "user1", "jwt1").await.unwrap();
|
.mount("/static", FileServer::from("static"))
|
||||||
db::insert_user(&conn, "user2", "jwt2").await.unwrap();
|
|
||||||
let authorization = db::Authorization {
|
|
||||||
id: 0,
|
|
||||||
from_date: "2024-05-01T00:00".to_string(),
|
|
||||||
to_date: "2024-05-10T23:59".to_string(),
|
|
||||||
from_user: "user1".to_string(),
|
|
||||||
to_user: "user2".to_string(),
|
|
||||||
namespace: "namespace".to_string(),
|
|
||||||
};
|
|
||||||
db::insert_authorization(&conn, &authorization).await.unwrap();
|
|
||||||
let result = db::get_authorization(&conn, 1).await;
|
|
||||||
assert!(result.is_ok(), "Get authorization operation failed");
|
|
||||||
let authorization = result.unwrap();
|
|
||||||
assert!(authorization.is_some(), "Authorization not found");
|
|
||||||
let authorization = authorization.unwrap();
|
|
||||||
assert_eq!(authorization.from_user, "user1", "From user does not match");
|
|
||||||
assert_eq!(authorization.to_user, "user2", "To user does not match");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_insert_vote() {
|
|
||||||
let conn = db::connect().await.unwrap();
|
|
||||||
db::init_db(&conn).await.unwrap();
|
|
||||||
db::insert_user(&conn, "user1", "jwt1").await.unwrap();
|
|
||||||
db::insert_election(&conn, &db::Election {
|
|
||||||
id: 0,
|
|
||||||
name: "Election 1".to_string(),
|
|
||||||
username: "user1".to_string(),
|
|
||||||
namespace: "namespace".to_string(),
|
|
||||||
description: "Description 1".to_string(),
|
|
||||||
start_date: "2024-05-01T08:00".to_string(),
|
|
||||||
end_date: "2024-05-10T20:00".to_string(),
|
|
||||||
}).await.unwrap();
|
|
||||||
let vote = db::Vote {
|
|
||||||
id: 0,
|
|
||||||
value: 1,
|
|
||||||
option_id: 1,
|
|
||||||
election_id: 1,
|
|
||||||
option_name: "Option 1".to_string(),
|
|
||||||
user: "user1".to_string(),
|
|
||||||
namespace: "namespace".to_string(),
|
|
||||||
date: "2024-05-01T12:00".to_string(),
|
|
||||||
};
|
|
||||||
let result = db::insert_vote(&conn, &vote).await;
|
|
||||||
assert!(result.is_ok(), "Insert vote operation failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_get_vote() {
|
|
||||||
let conn = db::connect().await.unwrap();
|
|
||||||
db::init_db(&conn).await.unwrap();
|
|
||||||
db::insert_user(&conn, "user1", "jwt1").await.unwrap();
|
|
||||||
db::insert_election(&conn, &db::Election {
|
|
||||||
id: 0,
|
|
||||||
name: "Election 1".to_string(),
|
|
||||||
username: "user1".to_string(),
|
|
||||||
namespace: "namespace".to_string(),
|
|
||||||
description: "Description 1".to_string(),
|
|
||||||
start_date: "2024-05-01T08:00".to_string(),
|
|
||||||
end_date: "2024-05-10T20:00".to_string(),
|
|
||||||
}).await.unwrap();
|
|
||||||
let vote = db::Vote {
|
|
||||||
id: 0,
|
|
||||||
value: 1,
|
|
||||||
option_id: 1,
|
|
||||||
election_id: 1,
|
|
||||||
option_name: "Option 1".to_string(),
|
|
||||||
user: "user1".to_string(),
|
|
||||||
namespace: "namespace".to_string(),
|
|
||||||
date: "2024-05-01T12:00".to_string(),
|
|
||||||
};
|
|
||||||
db::insert_vote(&conn, &vote).await.unwrap();
|
|
||||||
let result = db::get_vote(&conn, 1).await;
|
|
||||||
assert!(result.is_ok(), "Get vote operation failed");
|
|
||||||
let vote = result.unwrap();
|
|
||||||
assert!(vote.is_some(), "Vote not found");
|
|
||||||
let vote = vote.unwrap();
|
|
||||||
assert_eq!(vote.option_name, "Option 1", "Option name does not match");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
println!("This is the main function. Run `cargo test` to execute tests.");
|
|
||||||
}
|
}
|
|
@ -1,74 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Create Election</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
color: #9f9f9f;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
width: 25em;
|
|
||||||
margin: 3.125em auto;
|
|
||||||
padding: 1.25em;
|
|
||||||
background-color: #2b2b2b;
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
box-shadow: 0 0.125em 0.3125em rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 1.25em;
|
|
||||||
}
|
|
||||||
input[type="text"],
|
|
||||||
input[type="datetime-local"],
|
|
||||||
textarea,
|
|
||||||
input[type="submit"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.625em;
|
|
||||||
margin-bottom: 0.625em;
|
|
||||||
border: 0.0125em solid #868686;
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: #2b2b2b;
|
|
||||||
color: #9f9f9f;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
height: 6.25em;
|
|
||||||
background-color: #2b2b2b;
|
|
||||||
color: #9f9f9f;
|
|
||||||
}
|
|
||||||
input[type="submit"] {
|
|
||||||
background-color: #1a75ff; /* Darker blue */
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 0px;
|
|
||||||
}
|
|
||||||
input[type="submit"]:hover {
|
|
||||||
background-color: #145cbf; /* Even darker blue */
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h2>Create Election</h2>
|
|
||||||
<form id="electionForm" action="/elections/create" method="POST">
|
|
||||||
<label for="name">Name:</label>
|
|
||||||
<input type="text" id="name" name="name" placeholder="Enter election name" required>
|
|
||||||
<label for="description">Description:</label>
|
|
||||||
<textarea id="description" name="description" placeholder="Enter election description" required></textarea>
|
|
||||||
<label for="start_date">Start Date:</label>
|
|
||||||
<input type="datetime-local" id="start_date" name="start_date" required>
|
|
||||||
<label for="end_date">End Date:</label>
|
|
||||||
<input type="datetime-local" id="end_date" name="end_date" required>
|
|
||||||
<label for="namespace">Namespace:</label>
|
|
||||||
<input type="text" id="namespace" name="namespace" placeholder="Enter namespace" required>
|
|
||||||
<!-- Additional fields for election items can be added here -->
|
|
||||||
<input type="submit" value="Create Election">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,138 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Election Details</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
color: #9f9f9f;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
grid-column: span 2;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 1.25em;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
padding: 0.3125em 0.625em;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 6em;
|
|
||||||
}
|
|
||||||
.detail {
|
|
||||||
width: 48%; /* Adjust as needed */
|
|
||||||
padding: 0.5em;
|
|
||||||
background-color: #333; /* Dark gray */
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
box-sizing: border-box; /* Add this line */
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail.full-width {
|
|
||||||
width: 100%; /* Full width */
|
|
||||||
}
|
|
||||||
.detail.full-heigth {
|
|
||||||
height: 100%; /* Full height */
|
|
||||||
min-height: 8.25em; /* Minimum height */
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail.dates {
|
|
||||||
display: flex; /* Add this line */
|
|
||||||
justify-content: space-between; /* Add this line */
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail label {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail span {
|
|
||||||
color: #9f9f9f;
|
|
||||||
}
|
|
||||||
.go-btn {
|
|
||||||
background-color: #4CAF50; /* Green */
|
|
||||||
}
|
|
||||||
.delete-btn {
|
|
||||||
background-color: #f44336; /* Red */
|
|
||||||
}
|
|
||||||
.results-btn {
|
|
||||||
background-color: #008CBA; /* Blue */
|
|
||||||
}
|
|
||||||
.button-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: 50em;
|
|
||||||
margin: 5em auto;
|
|
||||||
padding: 3em;
|
|
||||||
background-color: #2b2b2b;
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
box-shadow: 0 0.125em 0.3125em rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
#electionDetails {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h2>Election Details</h2>
|
|
||||||
<div id="electionDetails">
|
|
||||||
<!-- Election details will be inserted here dynamically -->
|
|
||||||
</div>
|
|
||||||
<div class="button-container">
|
|
||||||
<button class="go-btn">Vote</button>
|
|
||||||
<button class="delete-btn" onclick="confirmDelete()">Delete</button>
|
|
||||||
<button class="results-btn" onclick="confirmResults()">Results</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
function confirmDelete() {
|
|
||||||
if (confirm("Are you sure you want to delete this election?")) {
|
|
||||||
// Implement the logic to delete the election
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmResults() {
|
|
||||||
if (confirm("Are you sure you want to view the results of this election?")) {
|
|
||||||
// Implement the logic to view the results
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dummy data for demonstration
|
|
||||||
const election = { id: 1, name: "Election 1", username: "user", namespace: "namespace2", description: "Description 1", start_date: "2024-05-01T08:00", end_date: "2024-05-10T20:00" };
|
|
||||||
|
|
||||||
// Function to render election details
|
|
||||||
function renderElectionDetails(election) {
|
|
||||||
const detailsDiv = document.querySelector("#electionDetails");
|
|
||||||
detailsDiv.innerHTML = `
|
|
||||||
<div class="detail"><label>ID:</label> <span>${election.id}</span></div>
|
|
||||||
<div class="detail"><label>User:</label> <span>${election.username}</span></div>
|
|
||||||
<div class="detail"><label>Name:</label> <span>${election.name}</span></div>
|
|
||||||
<div class="detail"><label>Namespace:</label> <span>${election.namespace}</span></div>
|
|
||||||
<div class="detail full-width full-heigth"><label>Description:</label> <span>${election.description}</span></div>
|
|
||||||
<div class="detail">Start Date:</label> <span>${new Date(election.start_date).toLocaleString()}</span></div>
|
|
||||||
<div class="detail">End Date:</label> <span>${new Date(election.end_date).toLocaleString()}</span></div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial rendering of election details
|
|
||||||
renderElectionDetails(election);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,114 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>View Elections</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
color: #9f9f9f;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
width: 60em;
|
|
||||||
margin: 5em auto;
|
|
||||||
padding: 3em;
|
|
||||||
background-color: #2b2b2b;
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
box-shadow: 0 0.125em 0.3125em rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 1.25em;
|
|
||||||
}
|
|
||||||
#searchInput {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.5em;
|
|
||||||
margin-bottom: 4em;
|
|
||||||
background-color: #363535;
|
|
||||||
color: #9f9f9f;
|
|
||||||
border: 0.0625em solid #9f9f9f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1em;
|
|
||||||
background-color: #333;
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
padding: 0.3125em 0.625em;
|
|
||||||
margin: 0 0.3125em;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.details-btn {
|
|
||||||
background-color: #4CAF50; /* Green */
|
|
||||||
}
|
|
||||||
.delete-btn {
|
|
||||||
background-color: #f44336; /* Red */
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h2>View Elections</h2>
|
|
||||||
<input id="searchInput" type="text" placeholder="Search elections..." oninput="searchElections(this.value)">
|
|
||||||
<div id="electionsContainer">
|
|
||||||
<!-- Election cards will be inserted here dynamically -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
const elections = [
|
|
||||||
{ name: "Election 1", username: "user", namespace: "namespace2" },
|
|
||||||
{ name: "Election 2", username: "user", namespace: "namespace" },
|
|
||||||
{ name: "Election 3", username: "user2", namespace: "namespace" }
|
|
||||||
];
|
|
||||||
const currentUsername = "user";
|
|
||||||
|
|
||||||
function renderElections(elections) {
|
|
||||||
const container = document.querySelector("#electionsContainer");
|
|
||||||
container.innerHTML = "";
|
|
||||||
elections.forEach(election => {
|
|
||||||
const card = document.createElement("div");
|
|
||||||
card.className = "card";
|
|
||||||
card.innerHTML = `
|
|
||||||
<div>
|
|
||||||
<h3>${election.name}</h3>
|
|
||||||
<p>Created by ${election.username} in ${election.namespace}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button class="details-btn" onclick="viewDetails('${election.name}')">Details</button>
|
|
||||||
${election.username === currentUsername ?
|
|
||||||
`<button class="delete-btn" onclick="if(confirm('Are you sure you want to delete this election?')) { deleteElection('${election.name}') }">Delete</button>`
|
|
||||||
: ''}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
container.appendChild(card);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function searchElections(query) {
|
|
||||||
const filtered = elections.filter(election => election.name.toLowerCase().includes(query.toLowerCase()));
|
|
||||||
renderElections(filtered);
|
|
||||||
}
|
|
||||||
|
|
||||||
function viewDetails(name) {
|
|
||||||
// Implement the logic to view the details of the election
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteElection(name) {
|
|
||||||
// Implement the logic to delete the election
|
|
||||||
}
|
|
||||||
|
|
||||||
renderElections(elections);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,66 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Give Acces</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
color: #9f9f9f;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
width: 20em;
|
|
||||||
margin: 6.25em auto;
|
|
||||||
padding: 2em;
|
|
||||||
background-color: #2b2b2b;
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
box-shadow: 0 0.125em 0.3125em rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 2em;
|
|
||||||
}
|
|
||||||
input[type="text"],
|
|
||||||
input[type="datetime-local"],
|
|
||||||
input[type="submit"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 1em;
|
|
||||||
margin-bottom: 0.625em;
|
|
||||||
border: 0.0625em solid #9f9f9f;
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: #2b2b2b;
|
|
||||||
color: #9f9f9f;
|
|
||||||
}
|
|
||||||
input[type="submit"] {
|
|
||||||
background-color: #1a75ff; /* Darker blue */
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 0px;
|
|
||||||
}
|
|
||||||
input[type="submit"]:hover {
|
|
||||||
background-color: #145cbf; /* Even darker blue */
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h2>Give acces</h2>
|
|
||||||
<form id="tokenForm" action="/auth/token" method="POST">
|
|
||||||
<label for="from_date">From Date:</label>
|
|
||||||
<input type="datetime-local" id="from_date" name="from_date" required>
|
|
||||||
<label for="to_date">To Date:</label>
|
|
||||||
<input type="datetime-local" id="to_date" name="to_date" required>
|
|
||||||
<label for="user">User:</label>
|
|
||||||
<input type="text" id="user" name="user" placeholder="Enter username" required>
|
|
||||||
<label for="namespace">Namespace:</label>
|
|
||||||
<input type="text" id="namespace" name="namespace" placeholder="Enter namespace" required>
|
|
||||||
<input type="submit" value="Give Acces">
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,59 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Login</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
color: #9f9f9f;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
width: 20em;
|
|
||||||
margin: 6.25em auto;
|
|
||||||
padding: 2em;
|
|
||||||
background-color: #2b2b2b;
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
box-shadow: 0 0.125em 0.3125em rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 1.25em;
|
|
||||||
}
|
|
||||||
input[type="text"],
|
|
||||||
input[type="password"],
|
|
||||||
input[type="submit"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.625em;
|
|
||||||
margin-bottom: 0.625em;
|
|
||||||
border: 0.0625em solid #9f9f9f;
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: #2b2b2b;
|
|
||||||
color: #9f9f9f;
|
|
||||||
}
|
|
||||||
input[type="submit"] {
|
|
||||||
background-color: #1a75ff; /* Darker blue */
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 0px;
|
|
||||||
}
|
|
||||||
input[type="submit"]:hover {
|
|
||||||
background-color: #145cbf; /* Even darker blue */
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h2>Login</h2>
|
|
||||||
<form id="loginForm" action="/auth/login" method="POST">
|
|
||||||
<input type="text" name="username" placeholder="Username" required>
|
|
||||||
<input type="password" name="password" placeholder="Password" required>
|
|
||||||
<input type="submit" value="Login">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,157 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Voting Page</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
color: #9f9f9f;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
width: 50em;
|
|
||||||
margin: 5em auto;
|
|
||||||
padding: 2em;
|
|
||||||
background-color: #2b2b2b;
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
box-shadow: 0 0.125em 0.3125em rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.election-card {
|
|
||||||
margin-bottom: 1.25em;
|
|
||||||
padding: 0.625em;
|
|
||||||
background-color: #3b3b3b;
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
box-shadow: 0 0.125em 0.3125em rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 1.25em;
|
|
||||||
}
|
|
||||||
.item {
|
|
||||||
margin-bottom: 0.625em;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.item input[type="checkbox"] {
|
|
||||||
margin-right: 0.625em;
|
|
||||||
background-color: #2b2b2b;
|
|
||||||
color: #9f9f9f;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
margin-left: 0.625em;
|
|
||||||
padding: 0.625em;
|
|
||||||
border: 0px;
|
|
||||||
border-radius: 0.3125em;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: #1a75ff; /* Darker blue */
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background-color: #145cbf; /* Even darker blue */
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h2>Voting Page</h2>
|
|
||||||
<!-- User cards will be inserted here dynamically -->
|
|
||||||
<div id="usersContainer"></div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
const items = [
|
|
||||||
{ id: 1, name: "Item 1" },
|
|
||||||
{ id: 2, name: "Item 2" },
|
|
||||||
{ id: 3, name: "Item 3" }
|
|
||||||
// Add more items as needed
|
|
||||||
];
|
|
||||||
|
|
||||||
const users = [
|
|
||||||
{ id: 1, name: "User 1" },
|
|
||||||
{ id: 2, name: "User 2" },
|
|
||||||
{ id: 3, name: "User 3" }
|
|
||||||
// Add more users as needed
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const usersContainer = document.getElementById("usersContainer");
|
|
||||||
|
|
||||||
users.forEach(user => {
|
|
||||||
const userCard = document.createElement("div");
|
|
||||||
userCard.className = "election-card";
|
|
||||||
userCard.innerHTML = `
|
|
||||||
<h3>${user.name}</h3>
|
|
||||||
<div class="selectedItemsContainer"></div>
|
|
||||||
<h3>Unselected Items</h3>
|
|
||||||
<div class="unselectedItemsContainer">
|
|
||||||
${items.map(item => `
|
|
||||||
<div class="item" id="item${user.id}-${item.id}">
|
|
||||||
<input type="checkbox" id="checkbox${user.id}-${item.id}" onchange="toggleSelection('${user.id}-${item.id}')">
|
|
||||||
<label for="checkbox${user.id}-${item.id}">${item.name}</label>
|
|
||||||
</div>
|
|
||||||
`).join("")}
|
|
||||||
</div>
|
|
||||||
<button onclick="submitVote(${user.id})">Submit Vote</button>
|
|
||||||
`;
|
|
||||||
usersContainer.appendChild(userCard);
|
|
||||||
});
|
|
||||||
|
|
||||||
function moveUp(itemId) {
|
|
||||||
const item = document.getElementById(`item${itemId}`);
|
|
||||||
const previousItem = item.previousElementSibling;
|
|
||||||
if (previousItem) {
|
|
||||||
item.parentNode.insertBefore(item, previousItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveDown(itemId) {
|
|
||||||
const item = document.getElementById(`item${itemId}`);
|
|
||||||
const nextItem = item.nextElementSibling;
|
|
||||||
if (nextItem) {
|
|
||||||
item.parentNode.insertBefore(nextItem, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleSelection(itemId) {
|
|
||||||
const item = document.getElementById(`item${itemId}`);
|
|
||||||
const checkbox = item.querySelector(`input[type="checkbox"]`);
|
|
||||||
const userCard = item.parentNode.parentNode;
|
|
||||||
const selectedItemsContainer = userCard.querySelector('.selectedItemsContainer');
|
|
||||||
const unselectedItemsContainer = userCard.querySelector('.unselectedItemsContainer');
|
|
||||||
|
|
||||||
if (checkbox.checked) {
|
|
||||||
const upButton = document.createElement('button');
|
|
||||||
upButton.onclick = function() { moveUp(itemId); };
|
|
||||||
upButton.textContent = 'Up';
|
|
||||||
|
|
||||||
const downButton = document.createElement('button');
|
|
||||||
downButton.onclick = function() { moveDown(itemId); };
|
|
||||||
downButton.textContent = 'Down';
|
|
||||||
|
|
||||||
item.appendChild(upButton);
|
|
||||||
item.appendChild(downButton);
|
|
||||||
|
|
||||||
selectedItemsContainer.appendChild(item);
|
|
||||||
} else {
|
|
||||||
const buttons = item.querySelectorAll('button');
|
|
||||||
buttons.forEach(button => button.remove());
|
|
||||||
|
|
||||||
unselectedItemsContainer.appendChild(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitVote(userId) {
|
|
||||||
const userCard = document.querySelector(`#user${userId}`);
|
|
||||||
const selectedItemsContainer = userCard.querySelector('.selectedItemsContainer');
|
|
||||||
const selectedItems = Array.from(selectedItemsContainer.children)
|
|
||||||
.map(item => item.id.split('-')[1]); // Get the item id
|
|
||||||
|
|
||||||
// Implement the logic to submit the vote with the selected items
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Voting List</title>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css">
|
||||||
|
<style>
|
||||||
|
#sortable { list-style-type: none; margin: 0; padding: 0; width: 60%; }
|
||||||
|
#sortable li { margin: 5px; padding: 5px; font-size: 1.2em; height: 2em; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Vote List</h2>
|
||||||
|
<ul id="sortable">
|
||||||
|
<li class="ui-state-default"><input type="checkbox"> Item 1</li>
|
||||||
|
<li class="ui-state-default"><input type="checkbox"> Item 2</li>
|
||||||
|
</ul>
|
||||||
|
<button id="add">Add Item</button>
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
|
||||||
|
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
||||||
|
<script>
|
||||||
|
$( function() {
|
||||||
|
$( "#sortable" ).sortable();
|
||||||
|
$( "#sortable" ).disableSelection();
|
||||||
|
|
||||||
|
let count = 3;
|
||||||
|
$('#add').click(function() {
|
||||||
|
if ($('#sortable li').length < 10) {
|
||||||
|
$('#sortable').append('<li class="ui-state-default"><input type="checkbox"> Item ' + count++ + '</li>');
|
||||||
|
} else {
|
||||||
|
alert('Threshold reached');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} );
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -8,6 +8,7 @@ use std::path::Path;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use stv_rs::parse::parse_election;
|
use stv_rs::parse::parse_election;
|
||||||
use stv_rs::meek::stv_droop;
|
use stv_rs::meek::stv_droop;
|
||||||
use stv_rs::types::Election;
|
use stv_rs::types::Election;
|
||||||
|
@ -28,6 +29,9 @@ pub fn blt_to_string_results(blt: String) -> String {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Ballot {
|
struct Ballot {
|
||||||
weight: i32,
|
weight: i32,
|
||||||
|
@ -81,10 +85,7 @@ pub fn election_data_to_blt(election_data: ElectionData) -> String {
|
||||||
return blt;
|
return blt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn csv_to_election_data(csv: &str, num_seats: i32, title: String) -> ElectionData {
|
pub fn csv_to_election_data(csv: &str, num_seats: i32, title: String) -> ElectionData {
|
||||||
//TODO: use a proper csv parser, instead of splitting by delimiter
|
|
||||||
|
|
||||||
//read the csv file and convert it to a ballot
|
//read the csv file and convert it to a ballot
|
||||||
let delimiter = ",";
|
let delimiter = ",";
|
||||||
|
|
||||||
|
@ -138,6 +139,7 @@ pub fn csv_to_election_data(csv: &str, num_seats: i32, title: String) -> Electio
|
||||||
}
|
}
|
||||||
ballots.push(ballot);
|
ballots.push(ballot);
|
||||||
}
|
}
|
||||||
|
|
||||||
//deduplicate the ballots
|
//deduplicate the ballots
|
||||||
let mut deduped_ballots: Vec<Ballot> = Vec::new();
|
let mut deduped_ballots: Vec<Ballot> = Vec::new();
|
||||||
for ballot in ballots {
|
for ballot in ballots {
|
||||||
|
@ -153,6 +155,7 @@ pub fn csv_to_election_data(csv: &str, num_seats: i32, title: String) -> Electio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ballots = deduped_ballots;
|
ballots = deduped_ballots;
|
||||||
|
|
||||||
let election_data = ElectionData {
|
let election_data = ElectionData {
|
||||||
num_candidates: num_candidates,
|
num_candidates: num_candidates,
|
||||||
num_seats: num_seats,
|
num_seats: num_seats,
|
||||||
|
|
Loading…
Reference in New Issue