Compare commits
9 Commits
readme-cla
...
main
Author | SHA1 | Date |
---|---|---|
Adrian Gunnar Lauterer | 408035c2eb | |
Adrian Gunnar Lauterer | ab616cb181 | |
Adrian Gunnar Lauterer | 8593af2441 | |
Adrian Gunnar Lauterer | 57e0cb6a88 | |
Adrian Gunnar Lauterer | f035de1145 | |
Adrian Gunnar Lauterer | b2a0f85563 | |
Adrian Gunnar Lauterer | 0246f22e3e | |
Adrian Gunnar Lauterer | e3cc07061c | |
Adrian Gunnar Lauterer | 1293fa0b87 |
|
@ -14,3 +14,7 @@ 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,15 +6,9 @@ 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
|
||||||
|
@ -23,5 +17,30 @@ 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,11 +1,22 @@
|
||||||
# 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 +nightly -Z unstable-options build --out-dir ./build
|
cargo -Z unstable-options build --out-dir ./build
|
||||||
```
|
```
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
# \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)
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
41
flake.nix
41
flake.nix
|
@ -1,28 +1,37 @@
|
||||||
{
|
{
|
||||||
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
|
||||||
pkgs = import nixpkgs {
|
systems = [
|
||||||
system = "x86_64-linux";
|
"x86_64-linux"
|
||||||
};
|
"aarch64-linux"
|
||||||
|
"aarch64-darwin"
|
||||||
|
];
|
||||||
|
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: let
|
||||||
|
toolchain = fenix.packages.${system}.complete;
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [
|
||||||
|
(_: super: let pkgs = fenix.inputs.nixpkgs.legacyPackages.${system}; in fenix.overlays.default pkgs pkgs)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in f system pkgs toolchain);
|
||||||
in {
|
in {
|
||||||
devShell.x86_64-linux = devenv.lib.mkShell {
|
devShell = forAllSystems (system: pkgs: toolchain: pkgs.mkShell {
|
||||||
inherit inputs pkgs;
|
packages = [
|
||||||
modules = [
|
(toolchain.withComponents [
|
||||||
{
|
"cargo" "rustc" "rustfmt" "clippy"
|
||||||
languages.rust = {
|
])
|
||||||
enable = true;
|
pkgs.openssl
|
||||||
channel = "stable";
|
pkgs.pkg-config
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
};
|
RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/library";
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,303 @@
|
||||||
|
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
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
#![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();
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,239 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
235
src/main.rs
235
src/main.rs
|
@ -1,61 +1,186 @@
|
||||||
#[macro_use]
|
// main.rs
|
||||||
extern crate rocket;
|
|
||||||
|
|
||||||
use rocket::form::Form;
|
mod db;
|
||||||
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;
|
|
||||||
|
|
||||||
mod voting;
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::db;
|
||||||
|
use tokio;
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
#[tokio::test]
|
||||||
struct Vote {
|
async fn test_connection() {
|
||||||
option: String,
|
let result = db::connect().await;
|
||||||
|
assert!(result.is_ok(), "Database connection failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_insert_user() {
|
||||||
|
let conn = db::connect().await.unwrap();
|
||||||
|
db::init_db(&conn).await.unwrap();
|
||||||
|
let result = db::insert_user(&conn, "test_user", "test_jwt").await;
|
||||||
|
assert!(result.is_ok(), "Insert user operation failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_user() {
|
||||||
|
let conn = db::connect().await.unwrap();
|
||||||
|
db::init_db(&conn).await.unwrap();
|
||||||
|
db::insert_user(&conn, "test_user", "test_jwt").await.unwrap();
|
||||||
|
let result = db::get_user(&conn, "test_user").await;
|
||||||
|
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]
|
||||||
|
async fn test_insert_election() {
|
||||||
|
let conn = db::connect().await.unwrap();
|
||||||
|
db::init_db(&conn).await.unwrap();
|
||||||
|
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]
|
||||||
|
async fn test_get_election() {
|
||||||
|
let conn = db::connect().await.unwrap();
|
||||||
|
db::init_db(&conn).await.unwrap();
|
||||||
|
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(),
|
||||||
|
};
|
||||||
|
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]
|
||||||
|
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]
|
||||||
|
async fn test_get_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(),
|
||||||
|
};
|
||||||
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Votes = Mutex<Writer<std::fs::File>>;
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
#[post("/vote", data = "<vote>")]
|
println!("This is the main function. Run `cargo test` to execute tests.");
|
||||||
fn vote(vote: Form<Vote>, votes: &State<Votes>) -> Redirect {
|
|
||||||
let mut writer = votes.lock().unwrap();
|
|
||||||
writer.write_record(&[vote.option.clone()]).unwrap();
|
|
||||||
writer.flush().unwrap();
|
|
||||||
|
|
||||||
Redirect::to("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/results")]
|
|
||||||
fn results() -> Json<HashMap<String, String>> {
|
|
||||||
let election_data = voting::csv_to_election_data("votes.csv", 1, "Election".to_string());
|
|
||||||
let blt = voting::election_data_to_blt(election_data);
|
|
||||||
let results = voting::run_blt(blt);
|
|
||||||
Json(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/")]
|
|
||||||
fn index() -> Template {
|
|
||||||
let context: HashMap<&str, &str> = HashMap::new();
|
|
||||||
Template::render("index", &context)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[launch]
|
|
||||||
fn rocket() -> _ {
|
|
||||||
let file = std::fs::OpenOptions::new()
|
|
||||||
.create(true)
|
|
||||||
.append(true)
|
|
||||||
.open("votes.csv")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let writer = csv::Writer::from_writer(file);
|
|
||||||
|
|
||||||
rocket::build()
|
|
||||||
.attach(Template::fairing())
|
|
||||||
.manage(Mutex::new(writer))
|
|
||||||
.mount("/", routes![index, vote])
|
|
||||||
.mount("/static", FileServer::from("static"))
|
|
||||||
}
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
<!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>
|
|
@ -0,0 +1,138 @@
|
||||||
|
<!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>
|
|
@ -0,0 +1,114 @@
|
||||||
|
<!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>
|
|
@ -0,0 +1,66 @@
|
||||||
|
<!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>
|
|
@ -0,0 +1,59 @@
|
||||||
|
<!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>
|
|
@ -0,0 +1,157 @@
|
||||||
|
<!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>
|
|
@ -1,37 +0,0 @@
|
||||||
<!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,7 +8,6 @@ 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;
|
||||||
|
@ -29,9 +28,6 @@ pub fn blt_to_string_results(blt: String) -> String {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Ballot {
|
struct Ballot {
|
||||||
weight: i32,
|
weight: i32,
|
||||||
|
@ -85,7 +81,10 @@ 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 = ",";
|
||||||
|
|
||||||
|
@ -139,7 +138,6 @@ 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 {
|
||||||
|
@ -155,7 +153,6 @@ 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