45 |
46 |
47 |
48 |
49 | {% endblock %}
50 |
--------------------------------------------------------------------------------
/formie/templates/forms/forms.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
11 |
New Form
12 | {% endblock %}
13 |
--------------------------------------------------------------------------------
/formie/templates/forms/new.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block header %}
4 |
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 |
10 |
11 |
Make results private.
12 |
Block non-logged in users from answering.
13 |
14 | {% endblock %}
15 |
--------------------------------------------------------------------------------
/formie/templates/forms/results.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
6 | ID |
7 | {% for question in schema %}
8 | {% if question["name"] %}
9 | {{ question["name"] }} |
10 | {% endif %}
11 | {% endfor %}
12 |
13 | {% for result in results %}
14 |
15 | {% for field in result %}
16 | {{ field }} |
17 | {% endfor %}
18 |
19 | {% endfor %}
20 |
21 |
Export as CSV
22 | {% endblock %}
23 |
--------------------------------------------------------------------------------
/formie/templates/forms/submission_successful.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 | Form submission successful!
5 |
6 |
7 | {% if can_view_results %}
8 | You can view the results by
clicking here.
9 | {% endif %}
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/formie/templates/index.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hacettepeoyt/formie/128446a4ab664d84d20af24e3a22cfadbf5ad814/formie/templates/index.html
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==2.1.3
2 | Werkzeug>=2.2,<3.0
3 | Flask-SQLAlchemy==2.5.1
4 | SQLAlchemy==1.4.48
5 | argon2-cffi==21.3.0
6 | passlib==1.7.4
7 |
--------------------------------------------------------------------------------
/setup-db.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 |
5 | from formie import create_app, models
6 |
7 |
8 | def main() -> None:
9 | try:
10 | version: int = int(sys.argv[1])
11 | except (IndexError, ValueError):
12 | print(f"USAGE: {sys.argv[0]}
")
13 | print()
14 | print("Setups or upgrades the database depending on the version argument.")
15 | print()
16 | print("0 - full setup")
17 | print("1 - form access control flags upgrade")
18 | sys.exit(1)
19 |
20 | if version == 0:
21 | with create_app().app_context():
22 | models.db.create_all()
23 | elif version == 1:
24 | with create_app().app_context():
25 | with models.db.engine.begin() as conn:
26 | conn.execute(
27 | "ALTER TABLE Form ADD COLUMN access_control_flags INT NOT NULL DEFAULT 0;"
28 | )
29 | else:
30 | print("ERROR: invalid version", file=sys.stderr)
31 | sys.exit(1)
32 |
33 |
34 | if __name__ == "__main__":
35 | main()
36 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | #[macro_use]
2 | extern crate rocket;
3 |
4 | use rocket::response::Redirect;
5 | use rocket_db_pools::{sqlx, Database};
6 |
7 | mod proxy;
8 |
9 | #[derive(Database)]
10 | #[database("db")]
11 | struct DB(sqlx::SqlitePool);
12 |
13 | #[get("/")]
14 | fn index() -> Redirect {
15 | Redirect::to("/forms/")
16 | }
17 |
18 | #[launch]
19 | fn rocket() -> _ {
20 | let config = rocket::Config::figment().merge((
21 | "databases.db.url",
22 | std::env::var("SQLALCHEMY_DATABASE_URI")
23 | .unwrap_or("sqlite:///formie.sqlite".to_string())
24 | .strip_prefix("sqlite:///")
25 | .expect("Malformed SQLALCHEMY_DATABASE_URI"),
26 | ));
27 |
28 | rocket::custom(config)
29 | .attach(DB::init())
30 | .mount("/", routes![index])
31 | .mount("/", proxy::ProxyHandler)
32 | .manage(proxy::SecondaryServer::new())
33 | }
34 |
--------------------------------------------------------------------------------
/src/proxy.rs:
--------------------------------------------------------------------------------
1 | use rocket::data::ToByteUnit;
2 | use rocket::http::Method;
3 | use rocket::response::Responder;
4 | use rocket::route::Handler;
5 | use rocket::route::Outcome;
6 | use rocket::Data;
7 | use rocket::Request;
8 | use rocket::Route;
9 |
10 | pub struct SecondaryServer {
11 | process: std::process::Child,
12 | }
13 |
14 | impl SecondaryServer {
15 | pub fn new() -> SecondaryServer {
16 | #[cfg(debug_assertions)]
17 | SecondaryServer {
18 | process: std::process::Command::new("/usr/bin/env")
19 | .args(if cfg!(debug_assertions) {
20 | vec!["python", "-m", "flask", "run", "-p 5241"]
21 | } else {
22 | vec![
23 | "python",
24 | "-m",
25 | "gunicorn",
26 | "--bind",
27 | "127.0.0.1:5241",
28 | "formie:create_app()",
29 | ]
30 | })
31 | .spawn()
32 | .expect("Failed to start secondary server"),
33 | }
34 | }
35 | }
36 |
37 | impl Drop for SecondaryServer {
38 | fn drop(&mut self) {
39 | eprintln!("Cleaning up secondary server...");
40 |
41 | match self.process.try_wait() {
42 | Ok(Some(_)) => (),
43 | _ => {
44 | eprintln!("Sending sigterm to secondary server.");
45 | unsafe {
46 | libc::kill(self.process.id() as i32, libc::SIGTERM);
47 | }
48 | }
49 | }
50 |
51 | let _ = self.process.wait();
52 | }
53 | }
54 |
55 | fn transform_method(method: rocket::http::Method) -> reqwest::Method {
56 | match method {
57 | rocket::http::Method::Get => reqwest::Method::GET,
58 | rocket::http::Method::Put => reqwest::Method::PUT,
59 | rocket::http::Method::Post => reqwest::Method::POST,
60 | rocket::http::Method::Delete => reqwest::Method::DELETE,
61 | rocket::http::Method::Options => reqwest::Method::OPTIONS,
62 | rocket::http::Method::Head => reqwest::Method::HEAD,
63 | rocket::http::Method::Trace => reqwest::Method::TRACE,
64 | rocket::http::Method::Connect => reqwest::Method::CONNECT,
65 | rocket::http::Method::Patch => reqwest::Method::PATCH,
66 | }
67 | }
68 |
69 | pub async fn upstream_request(
70 | uri: &str,
71 | method: reqwest::Method,
72 | req_ctx: &Request<'_>,
73 | body: Option>,
74 | ) -> reqwest::Response {
75 | let client = reqwest::Client::builder()
76 | .redirect(reqwest::redirect::Policy::none())
77 | .build()
78 | .unwrap();
79 |
80 | let mut req_headers = reqwest::header::HeaderMap::new();
81 | for h in req_ctx.headers().clone().iter() {
82 | req_headers.insert(
83 | reqwest::header::HeaderName::from_bytes(h.name.as_str().as_bytes()).unwrap(),
84 | h.value().parse().unwrap(),
85 | );
86 | }
87 |
88 | client
89 | .request(method, format!("http://127.0.0.1:5241{}", uri))
90 | .headers(req_headers)
91 | .body(if body.is_some() {
92 | body.unwrap()
93 | .open(8.mebibytes())
94 | .into_bytes()
95 | .await
96 | .unwrap()
97 | .value
98 | } else {
99 | vec![]
100 | }) // TODO: stream this
101 | .send()
102 | .await
103 | .unwrap()
104 | }
105 |
106 | struct WrappedResponse<'r>(rocket::response::Result<'r>);
107 |
108 | impl WrappedResponse<'_> {
109 | async fn from<'r>(req: &'r Request<'_>, data: Data<'r>) -> WrappedResponse<'r> {
110 | let res = upstream_request(
111 | &format!("{}", req.uri()),
112 | transform_method(req.method()),
113 | req,
114 | Some(data),
115 | )
116 | .await;
117 | let mut res2 = rocket::response::Response::build();
118 | res2.status(rocket::http::Status {
119 | code: res.status().as_u16(),
120 | });
121 |
122 | for (k, v) in res.headers().clone() {
123 | if let (Some(key), Ok(value)) = (k, v.to_str()) {
124 | res2.raw_header(key.as_str().to_owned(), value.to_owned());
125 | }
126 | }
127 |
128 | // TODO: stream this
129 | let data = res.bytes().await.unwrap();
130 |
131 | WrappedResponse(res2.sized_body(data.len(), std::io::Cursor::new(data)).ok())
132 | }
133 | }
134 |
135 | #[rocket::async_trait]
136 | impl<'r> Responder<'r, 'r> for WrappedResponse<'r> {
137 | fn respond_to(self, _: &'r Request<'_>) -> rocket::response::Result<'r> {
138 | self.0
139 | }
140 | }
141 |
142 | #[derive(Clone)]
143 | pub struct ProxyHandler;
144 |
145 | #[rocket::async_trait]
146 | impl Handler for ProxyHandler {
147 | async fn handle<'r>(&self, req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r> {
148 | Outcome::from(req, WrappedResponse::from(req, data).await)
149 | }
150 | }
151 |
152 | #[allow(clippy::from_over_into)]
153 | impl Into> for ProxyHandler {
154 | fn into(self) -> Vec {
155 | vec![
156 | Route::new(Method::Get, "/", self.clone()),
157 | Route::new(Method::Post, "/", self),
158 | ]
159 | }
160 | }
161 |
--------------------------------------------------------------------------------