start POSSE implementation, add example config file
This commit is contained in:
parent
5da977443d
commit
0aec5a3d98
|
@ -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 = "..."
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,34 +1,10 @@
|
||||||
|
use super::{Error, Result, StringBody};
|
||||||
use crate::{models, paseto, schema, web, MainDatabase};
|
use crate::{models, paseto, schema, web, MainDatabase};
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use rocket::{
|
use rocket::State;
|
||||||
data::{self, FromDataSimple},
|
|
||||||
http::Status,
|
|
||||||
request::Request,
|
|
||||||
response::Responder,
|
|
||||||
Data,
|
|
||||||
Outcome::*,
|
|
||||||
Response, State,
|
|
||||||
};
|
|
||||||
use rocket_contrib::json::Json;
|
use rocket_contrib::json::Json;
|
||||||
use rusty_ulid::generate_ulid_string;
|
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)]
|
#[derive(serde::Serialize)]
|
||||||
pub struct FrontChange {
|
pub struct FrontChange {
|
||||||
|
@ -41,7 +17,7 @@ pub struct FrontChange {
|
||||||
|
|
||||||
#[get("/switches?<count>&<page>")]
|
#[get("/switches?<count>&<page>")]
|
||||||
#[instrument(skip(conn), err)]
|
#[instrument(skip(conn), err)]
|
||||||
pub fn get_switches(
|
pub fn list(
|
||||||
conn: MainDatabase,
|
conn: MainDatabase,
|
||||||
count: Option<i64>,
|
count: Option<i64>,
|
||||||
page: Option<i64>,
|
page: Option<i64>,
|
||||||
|
@ -49,7 +25,7 @@ pub fn get_switches(
|
||||||
) -> Result<Json<Vec<FrontChange>>> {
|
) -> Result<Json<Vec<FrontChange>>> {
|
||||||
use schema::{members, switches};
|
use schema::{members, switches};
|
||||||
|
|
||||||
let count = count.unwrap_or(50);
|
let count = count.unwrap_or(30);
|
||||||
let page = page.unwrap_or(0);
|
let page = page.unwrap_or(0);
|
||||||
|
|
||||||
let count = if count < 100 { count } else { 100 };
|
let count = if count < 100 { count } else { 100 };
|
||||||
|
@ -79,7 +55,7 @@ pub fn get_switches(
|
||||||
|
|
||||||
#[get("/switches/current")]
|
#[get("/switches/current")]
|
||||||
#[instrument(skip(conn), err)]
|
#[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};
|
use schema::{members, switches};
|
||||||
|
|
||||||
let mut front: Vec<(models::Switch, models::Member)> = switches::table
|
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>")]
|
#[post("/switches/switch", data = "<who>")]
|
||||||
#[instrument(skip(conn, sc, pk), err)]
|
#[instrument(skip(conn, sc, pk), err)]
|
||||||
pub fn make_switch(
|
pub fn switch(
|
||||||
conn: MainDatabase,
|
conn: MainDatabase,
|
||||||
who: StringBody,
|
who: StringBody,
|
||||||
sc: State<web::switchcounter::Client>,
|
sc: State<web::switchcounter::Client>,
|
||||||
|
@ -166,11 +142,7 @@ pub fn make_switch(
|
||||||
|
|
||||||
#[get("/switches/<switch_id>")]
|
#[get("/switches/<switch_id>")]
|
||||||
#[instrument(skip(conn), err)]
|
#[instrument(skip(conn), err)]
|
||||||
pub fn get_switch(
|
pub fn get(tok: paseto::Token, conn: MainDatabase, switch_id: String) -> Result<Json<FrontChange>> {
|
||||||
tok: paseto::Token,
|
|
||||||
conn: MainDatabase,
|
|
||||||
switch_id: String,
|
|
||||||
) -> Result<Json<FrontChange>> {
|
|
||||||
use schema::{members, switches::dsl::switches};
|
use schema::{members, switches::dsl::switches};
|
||||||
|
|
||||||
let (switch, member): (models::Switch, models::Member) = switches
|
let (switch, member): (models::Switch, models::Member) = switches
|
||||||
|
@ -187,50 +159,3 @@ pub fn get_switch(
|
||||||
ended_at: switch.ended_at,
|
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -45,16 +45,17 @@ fn main() -> Result<()> {
|
||||||
.attach(paseto::ed25519_keypair())
|
.attach(paseto::ed25519_keypair())
|
||||||
.attach(web::pluralkit::Client::fairing())
|
.attach(web::pluralkit::Client::fairing())
|
||||||
.attach(web::switchcounter::Client::fairing())
|
.attach(web::switchcounter::Client::fairing())
|
||||||
|
.attach(web::discord_webhook::Client::fairing())
|
||||||
.mount("/metrics", prometheus)
|
.mount("/metrics", prometheus)
|
||||||
.mount("/", routes![botinfo])
|
.mount("/", routes![botinfo])
|
||||||
.mount(
|
.mount(
|
||||||
"/api",
|
"/api",
|
||||||
routes![
|
routes![
|
||||||
|
api::switch::current_front,
|
||||||
|
api::switch::get,
|
||||||
|
api::switch::list,
|
||||||
|
api::switch::switch,
|
||||||
api::get_members,
|
api::get_members,
|
||||||
api::get_switches,
|
|
||||||
api::get_switch,
|
|
||||||
api::get_current_front,
|
|
||||||
api::make_switch,
|
|
||||||
api::token_info
|
api::token_info
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -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()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod discord_webhook;
|
||||||
pub mod pluralkit;
|
pub mod pluralkit;
|
||||||
pub mod switchcounter;
|
pub mod switchcounter;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue