start POSSE implementation, add example config file

This commit is contained in:
Cadey Ratio 2020-11-04 09:32:22 -05:00
parent 5da977443d
commit 0aec5a3d98
6 changed files with 191 additions and 86 deletions

View File

@ -0,0 +1,42 @@
# This is all the config you need for this service
[global]
address = "0.0.0.0"
switchcounter_webhook = "..."
discord_webhook = ""
# I don't know how to generate these without using Go, sorry. These are hex-encoded
# ED25519 public/private keys.
[global.paseto]
public = ""
private = ""
# fetch this from PluralKit directly
[global.pluralkit]
system_id = ""
token = ""
# fetch these from Pluralkit directly
[global.pluralkit.mappings]
# why the fuck does twitter need 4 goddamn API keys holy shit
[global.twitter]
consumer_token = "..."
consumer_secret = "..."
api_key = "..."
api_secret = "..."
# This MIGHT work with pleroma, godspeed. Fetch these values from
# the API.
[global.mastodon]
instance = "..."
app_id = "..."
app_secret = "..."
token = "..."
account = "..."
# Get these values by creating an oAuth app
[global.reddit]
app_id = "..."
app_secret = "..."
username = "..."
password = "..."

78
backend/src/api/mod.rs Normal file
View File

@ -0,0 +1,78 @@
use crate::{models, paseto, schema, web, MainDatabase};
use diesel::prelude::*;
use rocket::{
data::{self, FromDataSimple},
http::Status,
request::Request,
response::Responder,
Data,
Outcome::*,
Response,
};
use rocket_contrib::json::Json;
use std::io::Read;
pub mod switch;
#[get("/members")]
#[instrument(skip(conn), err)]
pub fn get_members(tok: paseto::Token, conn: MainDatabase) -> Result<Json<Vec<models::Member>>> {
use schema::members;
let results = members::table
.load::<models::Member>(&*conn)
.map_err(Error::Database)?;
Ok(Json(results))
}
#[get("/token/info")]
pub fn token_info(tok: paseto::Token) -> Json<paseto::Token> {
Json(tok)
}
#[derive(Debug)]
pub struct StringBody(String);
impl StringBody {
fn unwrap(self) -> String {
self.0
}
}
impl FromDataSimple for StringBody {
type Error = String;
fn from_data(_req: &Request, data: Data) -> data::Outcome<Self, String> {
let mut contents = String::new();
if let Err(e) = data.open().take(256).read_to_string(&mut contents) {
return Failure((Status::InternalServerError, format!("{:?}", e)));
}
Success(StringBody(contents))
}
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("internal database error: {0}")]
Database(#[from] diesel::result::Error),
#[error("not found")]
NotFound,
#[error("web API interop error: {0}")]
Web(#[from] web::Error),
}
pub type Result<T = ()> = std::result::Result<T, Error>;
impl<'a> Responder<'a> for Error {
fn respond_to(self, _: &Request) -> ::std::result::Result<Response<'a>, Status> {
error!("{}", self);
match self {
Error::NotFound => Err(Status::NotFound),
_ => Err(Status::InternalServerError),
}
}
}

View File

@ -1,34 +1,10 @@
use super::{Error, Result, StringBody};
use crate::{models, paseto, schema, web, MainDatabase};
use chrono::prelude::*;
use diesel::prelude::*;
use rocket::{
data::{self, FromDataSimple},
http::Status,
request::Request,
response::Responder,
Data,
Outcome::*,
Response, State,
};
use rocket::State;
use rocket_contrib::json::Json;
use rusty_ulid::generate_ulid_string;
use std::io::Read;
#[get("/members")]
#[instrument(skip(conn), err)]
pub fn get_members(tok: paseto::Token, conn: MainDatabase) -> Result<Json<Vec<models::Member>>> {
use schema::members;
let results = members::table
.load::<models::Member>(&*conn)
.map_err(Error::Database)?;
Ok(Json(results))
}
#[get("/token/info")]
pub fn token_info(tok: paseto::Token) -> Json<paseto::Token> {
Json(tok)
}
#[derive(serde::Serialize)]
pub struct FrontChange {
@ -41,7 +17,7 @@ pub struct FrontChange {
#[get("/switches?<count>&<page>")]
#[instrument(skip(conn), err)]
pub fn get_switches(
pub fn list(
conn: MainDatabase,
count: Option<i64>,
page: Option<i64>,
@ -49,7 +25,7 @@ pub fn get_switches(
) -> Result<Json<Vec<FrontChange>>> {
use schema::{members, switches};
let count = count.unwrap_or(50);
let count = count.unwrap_or(30);
let page = page.unwrap_or(0);
let count = if count < 100 { count } else { 100 };
@ -79,7 +55,7 @@ pub fn get_switches(
#[get("/switches/current")]
#[instrument(skip(conn), err)]
pub fn get_current_front(conn: MainDatabase, tok: paseto::Token) -> Result<Json<FrontChange>> {
pub fn current_front(conn: MainDatabase, tok: paseto::Token) -> Result<Json<FrontChange>> {
use schema::{members, switches};
let mut front: Vec<(models::Switch, models::Member)> = switches::table
@ -103,7 +79,7 @@ pub fn get_current_front(conn: MainDatabase, tok: paseto::Token) -> Result<Json<
#[post("/switches/switch", data = "<who>")]
#[instrument(skip(conn, sc, pk), err)]
pub fn make_switch(
pub fn switch(
conn: MainDatabase,
who: StringBody,
sc: State<web::switchcounter::Client>,
@ -166,11 +142,7 @@ pub fn make_switch(
#[get("/switches/<switch_id>")]
#[instrument(skip(conn), err)]
pub fn get_switch(
tok: paseto::Token,
conn: MainDatabase,
switch_id: String,
) -> Result<Json<FrontChange>> {
pub fn get(tok: paseto::Token, conn: MainDatabase, switch_id: String) -> Result<Json<FrontChange>> {
use schema::{members, switches::dsl::switches};
let (switch, member): (models::Switch, models::Member) = switches
@ -187,50 +159,3 @@ pub fn get_switch(
ended_at: switch.ended_at,
}))
}
#[derive(Debug)]
pub struct StringBody(String);
impl StringBody {
fn unwrap(self) -> String {
self.0
}
}
impl FromDataSimple for StringBody {
type Error = String;
fn from_data(_req: &Request, data: Data) -> data::Outcome<Self, String> {
let mut contents = String::new();
if let Err(e) = data.open().take(256).read_to_string(&mut contents) {
return Failure((Status::InternalServerError, format!("{:?}", e)));
}
Success(StringBody(contents))
}
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("internal database error: {0}")]
Database(#[from] diesel::result::Error),
#[error("not found")]
NotFound,
#[error("web API interop error: {0}")]
Web(#[from] web::Error),
}
pub type Result<T = ()> = std::result::Result<T, Error>;
impl<'a> Responder<'a> for Error {
fn respond_to(self, _: &Request) -> ::std::result::Result<Response<'a>, Status> {
error!("{}", self);
match self {
Error::NotFound => Err(Status::NotFound),
_ => Err(Status::InternalServerError),
}
}
}

View File

@ -45,16 +45,17 @@ fn main() -> Result<()> {
.attach(paseto::ed25519_keypair())
.attach(web::pluralkit::Client::fairing())
.attach(web::switchcounter::Client::fairing())
.attach(web::discord_webhook::Client::fairing())
.mount("/metrics", prometheus)
.mount("/", routes![botinfo])
.mount(
"/api",
routes![
api::switch::current_front,
api::switch::get,
api::switch::list,
api::switch::switch,
api::get_members,
api::get_switches,
api::get_switch,
api::get_current_front,
api::make_switch,
api::token_info
],
)

View File

@ -0,0 +1,58 @@
use super::{Error, Result};
use rocket::fairing::AdHoc;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Clone, Debug, Deserialize)]
struct AllowedMentions {
parse: Vec<String>,
}
#[derive(Serialize, Clone, Debug, Deserialize)]
pub struct Body {
content: String,
allowed_mentions: AllowedMentions,
}
impl Body {
pub fn new<T>(body: T) -> Body
where
T: Into<String>,
{
Body {
content: body.into(),
allowed_mentions: AllowedMentions { parse: vec![] },
}
}
}
pub struct Client {
webhook_url: String,
}
impl Client {
pub fn new(webhook_url: String) -> Self {
Client {
webhook_url: webhook_url,
}
}
pub fn fairing() -> AdHoc {
AdHoc::on_attach("Switch Counter API", |rocket| {
let webhook_url = rocket.config().get_string("discord_webhook").unwrap();
Ok(rocket.manage(Client::new(webhook_url)))
})
}
pub fn send(&self, body: String) -> Result<()> {
let resp = ureq::post(&self.webhook_url).send_json(serde_json::to_value(Body::new(body))?);
if resp.ok() {
Ok(())
} else {
Err(match resp.synthetic_error() {
Some(why) => Error::UReq(why.to_string()),
None => Error::HttpStatus(resp.status()),
})
}
}
}

View File

@ -1,3 +1,4 @@
pub mod discord_webhook;
pub mod pluralkit;
pub mod switchcounter;