summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDennis Kobert <dennis@kobert.dev>2020-03-03 16:16:03 +0100
committerDennis Kobert <dennis@kobert.dev>2020-03-03 16:16:03 +0100
commit9f8f49283d5932de91d189cd5a72309807ece00b (patch)
treea0db9fd1c9a1d41fa594151da584bbb60758bca4
parentaee5ff0b295b3af68dfd0b78643ab5b7f41429a7 (diff)
Restructure the project
-rw-r--r--.spotify_token_cache.json1
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml1
-rw-r--r--Rocket.toml2
-rw-r--r--src/database.rs164
-rw-r--r--src/errors.rs64
-rw-r--r--src/main.rs50
-rw-r--r--src/serve.rs223
8 files changed, 332 insertions, 174 deletions
diff --git a/.spotify_token_cache.json b/.spotify_token_cache.json
deleted file mode 100644
index 73dfb08..0000000
--- a/.spotify_token_cache.json
+++ /dev/null
@@ -1 +0,0 @@
-{"access_token":"BQDlqFlWBJKZsZgdlJ4LxS7G5Jobm-FDM3c7_P5-bUVMmmHqLmTyiBTyyyo17Ld89Ufb6G2gX7RGfU7XBp7ETUsef1kiI90JjMpUm9IcrNEeF9k3PWKwbSB3a3uAbYBUHBbpasMEc3gPmw988AccHJU0_vbzPCg","token_type":"Bearer","expires_in":3600,"expires_at":1580272937,"refresh_token":"AQAogEno6XYAznGBbMFo-qKO2oLiw2fwwGvUIUJF3zjBXKwwVNBVC_UyN9b49lI77lyb_QtAaRjnK4_efL9_kDJ8GInoLJBcVy4qXVqHwJiduKqMzWFa4cUQh1L7YU_VIF0","scope":"playlist-read-private playlist-read-collaborative"} \ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index bdd3972..f3905b1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1708,6 +1708,7 @@ dependencies = [
"postgres 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rspotify 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-postgres 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index b2e6b41..b5b089a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,3 +12,4 @@ rocket = "0.4"
lazy_static = { version = "1.4"}
futures = "0.3"
postgres = "0.17"
+tokio-postgres = "0.5"
diff --git a/Rocket.toml b/Rocket.toml
index afd3d0f..725664f 100644
--- a/Rocket.toml
+++ b/Rocket.toml
@@ -1,6 +1,6 @@
[development]
address = "localhost"
-port = 8085
+port = 8000
#workers = [number of cpus * 2]
keep_alive = 5
log = "normal"
diff --git a/src/database.rs b/src/database.rs
new file mode 100644
index 0000000..326c1e0
--- /dev/null
+++ b/src/database.rs
@@ -0,0 +1,164 @@
+use crate::errors::Error;
+use postgres::{Client, NoTls};
+use std::sync::{Arc, Mutex};
+
+lazy_static! {
+ static ref CLIENT: Arc<Mutex<Client>> = Arc::new(Mutex::new(Client::connect(
+ "host=track_db user=postgres password=example",
+ NoTls
+ )?));
+}
+
+fn initialize_db() -> Result<(), Error> {
+ let mut client = CLIENT.lock()?;
+ client.batch_execute(
+ r#"
+ CREATE TABLE IF NOT EXISTS track (
+ track_id SERIAL PRIMARY KEY,
+ track_code TEXT NOT NULL UNIQUE,
+ name TEXT NOT NULL,
+ artist TEXT NOT NULL,
+ popularity int DEFAULT 50
+ );
+ CREATE TABLE IF NOT EXISTS suser (
+ user_id SERIAL PRIMARY KEY,
+ user_name TEXT NOT NULL UNIQUE
+ );
+ CREATE TABLE IF NOT EXISTS user_track_raw (
+ track_id int REFERENCES track (track_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ user_id int REFERENCES suser (user_id) ON UPDATE CASCADE ON DELETE CASCADE,
+ count int NOT NULL DEFAULT 1,
+ CONSTRAINT track_user_pkey PRIMARY KEY (track_id, user_id)
+ );
+
+ CREATE OR REPLACE VIEW user_track AS (
+ SELECT *, "count" / (
+ SELECT SUM("count")
+ FROM user_track ut
+ WHERE ut.user_id = user_id
+ )::decimal AS score
+ FROM user_track
+ );
+ "#,
+ )?;
+ Ok(())
+}
+
+use rspotify::spotify::model::track::FullTrack;
+fn insert_track(user_id: i32, track: FullTrack) -> Result<(), Error> {
+ let mut client = crate::CLIENT.lock()?;
+
+ if track.id.is_none() {
+ println!("{:#?}", track);
+ return Err("failed to load get track information".into());
+ }
+ print!(" {} ", track.id.clone()?);
+ client.execute(
+ "INSERT INTO track (track_code, name, artist, popularity)
+ VALUES ($1, $2, $3, $4)
+ ON CONFLICT DO NOTHING
+ ",
+ &[
+ &(track.id.clone()?),
+ &track.name,
+ &track.artists[0].name,
+ &(track.popularity as i32),
+ ],
+ )?;
+ let track_id: i32 = client.query(
+ "SELECT track_id FROM track where track_code = $1;",
+ &[&(track.id?)],
+ )?[0]
+ .get(0);
+ println!("uid: {} tid: {}", user_id, track_id);
+ client.batch_execute(
+ "
+ INSERT INTO user_track_raw (track_id, user_id, count)
+ VALUES ($1, $2, $3)
+ ON CONFLICT
+ ON CONSTRAINT track_user_pkey
+ DO NOTHING;
+ UPDATE user_track SET count = count + 1 WHERE track_id = $1 AND user_id = $2;
+ ",
+ &[&track_id, &user_id, &0],
+ )?;
+ Ok(())
+}
+
+fn insert_user(name: &str) -> Result<(i32, String), Error> {
+ let mut client = crate::CLIENT.lock()?;
+ client.batch_execute(
+ "
+ INSERT INTO suser (user_name) VALUES ($1) ON CONFLICT (user_name) DO NOTHING;
+ DELETE FROM user_track WHERE user_name = $1;
+ ",
+ &[&user_id],
+ )?;
+ let user_id: i32 = client
+ .query_one(
+ "SELECT user_id FROM suser WHERE user_name = $1;",
+ &[&user_id],
+ )?
+ .get(0);
+ //reset user_track relation
+ Ok((user_id, name))
+}
+
+fn get_uid(name: &str, client: &mut postgres::Client) -> Result<i32, Error> {
+ let x: i32 = client
+ .query_one("SELECT user_id FROM suser where user_name = $1;", &[&name])?
+ .get(0);
+ Ok(x)
+}
+
+lazy_static! {
+ static ref CACHE: Arc<Mutex<HashMap<String, SpotifyOAuth>>> =
+ Arc::new(Mutex::new(HashMap::new()));
+}
+
+pub fn match_users(name1: String, name2: String) -> Result<String, Error> {
+ let mut client = crate::CLIENT.lock()?;
+ let mut songs = String::new();
+ for row in client.query(
+ "
+ WITH users AS (
+ SELECT *
+ FROM ( VALUES
+
+ ($1), ($2)
+
+ ) AS _ (user_id)
+ )
+ SELECT track_id, name, artist
+ FROM track
+ JOIN (
+ SELECT track_id
+ FROM user_track
+ JOIN suser USING (user_id)
+ JOIN track USING (track_id)
+ WHERE suser.user_name IN (SELECT * FROM users)
+ GROUP BY track_id
+ HAVING COUNT(track_id) = (SELECT COUNT(*) FROM users)
+ ORDER BY SUM(score) DESC
+ ) AS _ USING (track_id)
+ ;
+ ",
+ &[&name1.as_str(), &name2.as_str()],
+ )? {
+ let name: String = row.get(1);
+ let artist: String = row.get(2);
+ songs = format!("{}{} by {}\n", songs, name, artist);
+ }
+ Ok(songs)
+}
+
+#[get("/user")]
+pub fn get_users() -> Result<String, Error> {
+ let mut client = crate::CLIENT.lock()?;
+ let mut users = String::new();
+ for row in client.query("SELECT user_name FROM suser", &[])? {
+ let user: String = row.get(0);
+ users = format!("{}{}\n", users, user);
+ }
+ Ok(users)
+}
diff --git a/src/errors.rs b/src/errors.rs
new file mode 100644
index 0000000..d9893ba
--- /dev/null
+++ b/src/errors.rs
@@ -0,0 +1,64 @@
+use rocket::http::ContentType;
+use rocket::request::Request;
+use rocket::response::{self, Responder, Response};
+use rspotify::spotify::client::ApiError;
+use rspotify::spotify::oauth2::SpotifyOAuth;
+use std::io::Cursor;
+use std::sync::{MutexGuard, PoisonError};
+use tokio_postgres::error::Error as DbError;
+
+#[derive(Debug)]
+pub enum Error {
+ Postgres(DbError),
+ Spotify(ApiError),
+ Misc(String),
+}
+impl From<DbError> for Error {
+ fn from(error: DbError) -> Self {
+ Error::Postgres(error)
+ }
+}
+impl From<ApiError> for Error {
+ fn from(error: ApiError) -> Self {
+ Error::Spotify(error)
+ }
+}
+impl From<&str> for Error {
+ fn from(error: &str) -> Self {
+ Error::Misc(error.to_owned())
+ }
+}
+impl From<String> for Error {
+ fn from(error: String) -> Self {
+ Error::Misc(error)
+ }
+}
+impl<'a> From<PoisonError<MutexGuard<'a, HashMap<String, SpotifyOAuth>>>> for Error {
+ fn from(error: PoisonError<MutexGuard<'a, HashMap<String, SpotifyOAuth>>>) -> Self {
+ Error::Misc(format!("failed to lock the client mutex: {:?}", error))
+ }
+}
+impl<'a> From<PoisonError<MutexGuard<'a, postgres::Client>>> for Error {
+ fn from(error: PoisonError<MutexGuard<'a, postgres::Client>>) -> Self {
+ Error::Misc(format!("failed to lock the client mutex: {:?}", error))
+ }
+}
+impl<'a> From<std::option::NoneError> for Error {
+ fn from(error: std::option::NoneError) -> Self {
+ Error::Misc(format!("tried to unwrap none at: {:?}", error))
+ }
+}
+impl<'a> Responder<'a> for Error {
+ fn respond_to(self, _: &Request) -> response::Result<'a> {
+ let response = match self {
+ Error::Postgres(e) => format!("DB Error: {:?}", e),
+ Error::Spotify(e) => format!("Spotify Error: {:?}", e),
+ Error::Misc(e) => format!("Error: {}", e),
+ };
+ Response::build()
+ .header(ContentType::Plain)
+ .status(rocket::http::Status::raw(500))
+ .sized_body(Cursor::new(response))
+ .ok()
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index d1ebe7b..93a22a6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,21 +1,17 @@
#![feature(proc_macro_hygiene, decl_macro)]
+#![feature(try_trait)]
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate lazy_static;
+mod database;
+mod errors;
mod serve;
-use postgres::{Client, NoTls};
-use std::sync::{Arc, Mutex};
-
-lazy_static! {
- static ref CLIENT: Arc<Mutex<Client>> = Arc::new(Mutex::new(
- Client::connect("host=track_db user=postgres password=example", NoTls).unwrap()
- ));
-}
+mod spotify;
fn main() {
- //initialize_db().unwrap();
+ //database::initialize_db().unwrap();
println!("connected with db");
rocket::ignite()
.mount(
@@ -29,39 +25,3 @@ fn main() {
)
.launch();
}
-
-fn initialize_db() -> Result<(), postgres::Error> {
- let mut client = CLIENT.lock().unwrap();
- //let _ = client.batch_execute("DROP TABLE user_track; DROP TABLE suser; DROP TABLE track;");
- client.batch_execute(
- r#"
- CREATE TABLE track IF NOT EXISTS (
- track_id SERIAL PRIMARY KEY,
- track_code TEXT NOT NULL UNIQUE,
- name TEXT NOT NULL,
- artist TEXT NOT NULL,
- popularity int DEFAULT 50
- );
- CREATE TABLE suser IF NOT EXISTS (
- user_id SERIAL PRIMARY KEY,
- user_name TEXT NOT NULL UNIQUE
- );
- CREATE TABLE user_track_raw IF NOT EXISTS (
- track_id int REFERENCES track (track_id) ON UPDATE CASCADE ON DELETE CASCADE,
- user_id int REFERENCES suser (user_id) ON UPDATE CASCADE ON DELETE CASCADE,
- count int NOT NULL DEFAULT 1,
- CONSTRAINT track_user_pkey PRIMARY KEY (track_id, user_id)
- );
-
- CREATE OR REPLACE VIEW user_track AS (
- SELECT *, "count" / (
- SELECT SUM("count")
- FROM user_track ut
- WHERE ut.user_id = user_id
- )::decimal AS score
- FROM user_track
- );
- "#,
- )?;
- Ok(())
-}
diff --git a/src/serve.rs b/src/serve.rs
index 95ce9b1..7014641 100644
--- a/src/serve.rs
+++ b/src/serve.rs
@@ -1,6 +1,6 @@
+use crate::errors::Error;
use lazy_static::lazy_static;
-use rocket::http::Status;
-use rocket::response::{status, Redirect};
+use rocket::response::Redirect;
use rspotify::spotify::client::{ApiError, Spotify};
use rspotify::spotify::oauth2::{SpotifyClientCredentials, SpotifyOAuth};
use rspotify::spotify::util::process_token;
@@ -13,47 +13,12 @@ lazy_static! {
}
#[get("/callback/<name>/<url>")]
-pub fn get_tracks(name: String, url: String) -> Result<(), status::Custom<String>> {
- let mut guard = CACHE.lock().or(Err(status::Custom(
- Status::InternalServerError,
- String::from("failed to lock cache mutex"),
- )))?;
- let mut oauth = guard.remove(&name).ok_or(status::Custom(
- Status::NotFound,
- String::from("uuid not found in cache"),
- ))?;
- println!("auth: {:?} url: {}", oauth, url);
- let token_info = process_token(&mut oauth, &mut ("?code=".to_owned() + url.as_ref()));
- let client_credential = SpotifyClientCredentials::default()
- .token_info(token_info.unwrap())
- .build();
-
- let spotify = Spotify::default()
- .client_credentials_manager(client_credential)
- .build();
- let user_id = spotify.current_user().unwrap().id;
- let mut client = crate::CLIENT.lock().or(Err(status::Custom(
- Status::InternalServerError,
- String::from("failed to lock cache mutex"),
- )))?;
- client
- .execute(
- "INSERT INTO suser (user_name) VALUES ($1) ON CONFLICT (user_name) DO NOTHING;",
- &[&user_id],
- )
- .unwrap();
- let uid = get_uid(user_id.as_ref(), &mut client).ok_or(status::Custom(
- Status::NotFound,
- format!("username {} not found", user_id),
- ))?;
- client
- .execute("DELETE FROM user_track WHERE user_id = $1;", &[&uid])
- .unwrap();
- drop(client);
+pub fn get_tracks(name: String, url: String) -> Result<(), Error> {
+ let (uid, spotify_uid, spotify) = autenth_user(name.as_ref(), url)?;
let chunk_size = 50;
let mut playlist_index = 0;
loop {
- match spotify.user_playlists(user_id.as_ref(), Some(chunk_size), Some(playlist_index)) {
+ match spotify.user_playlists(spotify_uid.as_ref(), Some(chunk_size), Some(playlist_index)) {
Ok(playlists) => {
playlist_index += chunk_size;
if playlists.items.is_empty() {
@@ -65,7 +30,7 @@ pub fn get_tracks(name: String, url: String) -> Result<(), status::Custom<String
loop {
match spotify.user_playlist_tracks(
- user_id.as_ref(),
+ spotify_uid.as_ref(),
&playlist.id,
None,
Some(chunk_size),
@@ -78,49 +43,9 @@ pub fn get_tracks(name: String, url: String) -> Result<(), status::Custom<String
break;
}
for track in tracks.items {
- //println!("{:?}", track.track.name);
- let mut client = crate::CLIENT.lock().or(Err(
- status::Custom(Status::InternalServerError, String::new()),
- ))?;
-
- if track.track.id.is_none() {
- println!("{:#?}", track);
- continue;
- }
- print!(" {} ", track.track.id.clone().unwrap());
- client
- .execute(
- "INSERT INTO track (track_code, name, artist, popularity)
- VALUES ($1, $2, $3, $4)
- ON CONFLICT DO NOTHING
- ",
- &[&(track.track.id.clone().unwrap()), &track.track.name, &track.track.artists[0].name, &(track.track.popularity as i32)],
- )
- .unwrap();
- let tid: i32 = client
- .query(
- "SELECT track_id FROM track where track_code = $1;",
- &[&(track.track.id.clone().unwrap())],
- )
- .unwrap()[0]
- .get(0);
- println!("uid: {} tid: {}", uid, tid);
- client
- .execute(
- "INSERT INTO user_track (track_id, user_id, count)
- VALUES ($1, $2, $3)
- ON CONFLICT
- ON CONSTRAINT track_user_pkey
- DO NOTHING;",
- &[&tid, &uid, &0],
- )
- .unwrap();
- client
- .execute(
- "UPDATE user_track SET count = count + 1 WHERE track_id = $1 AND user_id = $2;",
- &[&tid, &uid],
- )
- .unwrap();
+ if let Err(e) = insert_track(uid, track.track) {
+ println!("failed to load track to db: {:?}", e)
+ };
}
}
Err(e) => match e.downcast::<ApiError>() {
@@ -152,52 +77,100 @@ pub fn get_tracks(name: String, url: String) -> Result<(), status::Custom<String
Ok(())
}
+fn autenth_user(name: &str, url: String) -> Result<(i32, String, Spotify), Error> {
+ let mut guard = CACHE.lock()?;
+ let mut oauth = guard.remove(name)?;
+ println!("auth: {:?} url: {}", oauth, url);
+ let token_info = process_token(&mut oauth, &mut ("?code=".to_owned() + url.as_ref()));
+ let client_credential = SpotifyClientCredentials::default()
+ .token_info(token_info?)
+ .build();
+
+ let spotify = Spotify::default()
+ .client_credentials_manager(client_credential)
+ .build();
+ let user_id = spotify
+ .current_user()
+ .map_err(|e| format!("failed to load currentuser {:?}", e))?
+ .id;
+ let mut client = crate::CLIENT.lock()?;
+ client.execute(
+ "INSERT INTO suser (user_name) VALUES ($1) ON CONFLICT (user_name) DO NOTHING;",
+ &[&user_id],
+ )?;
+ let uid = get_uid(user_id.as_ref(), &mut client)?;
+ //reset user_track relation
+ client.execute("DELETE FROM user_track WHERE user_id = $1;", &[&uid])?;
+ Ok((uid, user_id, spotify))
+}
+
+use rspotify::spotify::model::track::FullTrack;
+fn insert_track(user_id: i32, track: FullTrack) -> Result<(), Error> {
+ let mut client = crate::CLIENT.lock()?;
+
+ if track.id.is_none() {
+ println!("{:#?}", track);
+ return Err("failed to load get track information".into());
+ }
+ print!(" {} ", track.id.clone()?);
+ client.execute(
+ "INSERT INTO track (track_code, name, artist, popularity)
+ VALUES ($1, $2, $3, $4)
+ ON CONFLICT DO NOTHING
+ ",
+ &[
+ &(track.id.clone()?),
+ &track.name,
+ &track.artists[0].name,
+ &(track.popularity as i32),
+ ],
+ )?;
+ let track_id: i32 = client.query(
+ "SELECT track_id FROM track where track_code = $1;",
+ &[&(track.id?)],
+ )?[0]
+ .get(0);
+ println!("uid: {} tid: {}", user_id, track_id);
+ client.execute(
+ "
+ INSERT INTO user_track_raw (track_id, user_id, count)
+ VALUES ($1, $2, $3)
+ ON CONFLICT
+ ON CONSTRAINT track_user_pkey
+ DO NOTHING;
+ UPDATE user_track SET count = count + 1 WHERE track_id = $1 AND user_id = $2;
+ ",
+ &[&track_id, &user_id, &0],
+ )?;
+ Ok(())
+}
+
#[get("/token/<name>")]
-pub fn token(name: String) -> Result<Redirect, status::Custom<String>> {
+pub fn token(name: String) -> Result<Redirect, Error> {
let state = rspotify::spotify::util::generate_random_string(16);
let oauth = SpotifyOAuth::default();
- //let callback = oauth.redirect_uri.clone();
let oauth = oauth
.scope("playlist-read-private, playlist-read-collaborative, user-read-private, user-follow-read, user-library-read")
- //.redirect_uri(format!("{}/{}", callback, &state).as_ref())
.build();
let auth_url = oauth.get_authorize_url(Some(&state), None);
- match CACHE.lock() {
- Ok(mut guard) => {
- guard.insert(name, oauth);
- Ok(Redirect::to(auth_url))
- }
- Err(_) => Err(status::Custom(
- Status::ImATeapot,
- "Internal Server Error".to_owned(),
- )),
- }
+ let mut guard = CACHE.lock()?;
+ guard.insert(name, oauth);
+ Ok(Redirect::to(auth_url))
}
-fn get_uid(name: &str, client: &mut postgres::Client) -> Option<i32> {
- match client.query("SELECT user_id FROM suser where user_name = $1;", &[&name]) {
- Ok(rows) => match rows.len() {
- 0 => None,
- x => {
- let x: i32 = rows[0].get(0);
- Some(x)
- }
- },
- _ => None,
- }
+fn get_uid(name: &str, client: &mut postgres::Client) -> Result<i32, Error> {
+ let x: i32 = client
+ .query_one("SELECT user_id FROM suser where user_name = $1;", &[&name])?
+ .get(0);
+ Ok(x)
}
#[get("/match/<name1>/<name2>")]
-pub fn match_users(name1: String, name2: String) -> Result<String, status::NotFound<String>> {
- let mut client = crate::CLIENT.lock().unwrap();
- let uid1 = get_uid(name1.as_ref(), &mut client)
- .ok_or(status::NotFound(format!("username {} not found", name1)))?;
- let uid2 = get_uid(name2.as_ref(), &mut client)
- .ok_or(status::NotFound(format!("username {} not found", name2)))?;
+pub fn match_users(name1: String, name2: String) -> Result<String, Error> {
+ let mut client = crate::CLIENT.lock()?;
let mut songs = String::new();
- for row in client
- .query(
- "
+ for row in client.query(
+ "
WITH users AS (
SELECT *
FROM ( VALUES
@@ -213,19 +186,15 @@ pub fn match_users(name1: String, name2: String) -> Result<String, status::NotFo
FROM user_track
JOIN suser USING (user_id)
JOIN track USING (track_id)
- WHERE suser.user_id IN (SELECT * FROM users)
+ WHERE suser.user_name IN (SELECT * FROM users)
GROUP BY track_id
HAVING COUNT(track_id) = (SELECT COUNT(*) FROM users)
ORDER BY SUM(score) DESC
) AS _ USING (track_id)
;
",
- &[&uid1, &uid2],
- )
- .unwrap()
- {
- //let song = row.get(0);
- println!("{:?}", row.columns());
+ &[&name1.as_str(), &name2.as_str()],
+ )? {
let name: String = row.get(1);
let artist: String = row.get(2);
songs = format!("{}{} by {}\n", songs, name, artist);
@@ -234,10 +203,10 @@ pub fn match_users(name1: String, name2: String) -> Result<String, status::NotFo
}
#[get("/user")]
-pub fn get_users() -> Result<String, status::NotFound<String>> {
- let mut client = crate::CLIENT.lock().unwrap();
+pub fn get_users() -> Result<String, Error> {
+ let mut client = crate::CLIENT.lock()?;
let mut users = String::new();
- for row in client.query("SELECT user_name FROM suser", &[]).unwrap() {
+ for row in client.query("SELECT user_name FROM suser", &[])? {
let user: String = row.get(0);
users = format!("{}{}\n", users, user);
}