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
|
||||
*.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
|
||||
|
||||
[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
|
||||
rand = "0.8.5"
|
||||
# for json serialization
|
||||
serde_json = "1.0.113"
|
||||
# for uuid generation
|
||||
uuid = { version = "1.7.0", features = ["serde", "v4"] }
|
||||
# for command line arguments
|
||||
|
@ -17,30 +23,5 @@ structopt = "0.3.23"
|
|||
csv = "1.1.6"
|
||||
clap = { version = "4.3.24", features = ["derive"] }
|
||||
|
||||
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 = "*"
|
||||
stv-rs = "0.3.0"
|
15
README.md
15
README.md
|
@ -1,22 +1,11 @@
|
|||
# Vote-rs
|
||||
|
||||
Currently in development....
|
||||
|
||||
## current functionality
|
||||
Currently in development
|
||||
|
||||
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
|
||||
|
||||
```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
|
||||
}
|
41
flake.nix
41
flake.nix
|
@ -1,37 +1,28 @@
|
|||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
devenv.url = "github:cachix/devenv";
|
||||
|
||||
fenix.url = "github:nix-community/fenix";
|
||||
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
|
||||
outputs = { self, nixpkgs, fenix }@inputs:
|
||||
outputs = { self, nixpkgs, devenv, ... }@inputs:
|
||||
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 {
|
||||
inherit system;
|
||||
overlays = [
|
||||
(_: super: let pkgs = fenix.inputs.nixpkgs.legacyPackages.${system}; in fenix.overlays.default pkgs pkgs)
|
||||
];
|
||||
};
|
||||
in f system pkgs toolchain);
|
||||
pkgs = import nixpkgs {
|
||||
system = "x86_64-linux";
|
||||
};
|
||||
in {
|
||||
devShell = forAllSystems (system: pkgs: toolchain: pkgs.mkShell {
|
||||
packages = [
|
||||
(toolchain.withComponents [
|
||||
"cargo" "rustc" "rustfmt" "clippy"
|
||||
])
|
||||
pkgs.openssl
|
||||
pkgs.pkg-config
|
||||
devShell.x86_64-linux = devenv.lib.mkShell {
|
||||
inherit inputs pkgs;
|
||||
modules = [
|
||||
{
|
||||
languages.rust = {
|
||||
enable = true;
|
||||
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)
|
||||
}
|
||||
}
|
235
src/main.rs
235
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 tests {
|
||||
use super::db;
|
||||
use tokio;
|
||||
mod voting;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_connection() {
|
||||
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");
|
||||
}
|
||||
#[derive(rocket::FromForm)]
|
||||
struct Vote {
|
||||
option: String,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("This is the main function. Run `cargo test` to execute tests.");
|
||||
type Votes = Mutex<Writer<std::fs::File>>;
|
||||
|
||||
#[post("/vote", data = "<vote>")]
|
||||
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"))
|
||||
}
|
|
@ -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::meek::stv_droop;
|
||||
use stv_rs::types::Election;
|
||||
|
@ -28,6 +29,9 @@ pub fn blt_to_string_results(blt: String) -> String {
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Ballot {
|
||||
weight: i32,
|
||||
|
@ -81,10 +85,7 @@ pub fn election_data_to_blt(election_data: ElectionData) -> String {
|
|||
return blt;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
let delimiter = ",";
|
||||
|
||||
|
@ -138,6 +139,7 @@ pub fn csv_to_election_data(csv: &str, num_seats: i32, title: String) -> Electio
|
|||
}
|
||||
ballots.push(ballot);
|
||||
}
|
||||
|
||||
//deduplicate the ballots
|
||||
let mut deduped_ballots: Vec<Ballot> = Vec::new();
|
||||
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;
|
||||
|
||||
let election_data = ElectionData {
|
||||
num_candidates: num_candidates,
|
||||
num_seats: num_seats,
|
||||
|
|
Loading…
Reference in New Issue