inital commit

This commit is contained in:
zoe 2023-08-24 20:21:37 +02:00
commit 80e708d05a
47 changed files with 17312 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

2892
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

42
Cargo.toml Normal file
View File

@ -0,0 +1,42 @@
[package]
name = "starchart"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1", features = ["full"] }
axum = "0.6"
aide = { version = "0.11", features = [
"axum",
"axum-extra",
"redoc",
"macros",
"axum-ws",
"axum-multipart",
"axum-headers",
"axum-extra-cookie",
"axum-extra-cookie-private",
"axum-extra-form",
"axum-extra-query",
] }
rust-embed = { version = "8" }
confy = "0.5"
schemars = { version = "0.8", features = ["derive_json_schema", "uuid1"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = [
"env-filter",
"fmt",
"ansi",
] }
once_cell = "1"
sqlx = { version = "0.7", features = ["runtime-tokio", "tls-rustls", "postgres", "macros"] }
serde = "1.0.186"
serde_derive = "1.0.186"
axum-jsonschema = { version = "0.6.0", features = ["aide"] }
axum-macros = "0.3.8"
tinyrand = "0.5.0"
tinyrand-std = "0.5.0"
mime_guess = "2.0.4"
hyper = "0.14.27"

5
build.rs Normal file
View File

@ -0,0 +1,5 @@
// generated by `sqlx migrate build-script`
fn main() {
// trigger recompilation when a new migration is added
println!("cargo:rerun-if-changed=migrations");
}

View File

@ -0,0 +1,8 @@
create table stars (
id Integer Primary Key Generated Always as Identity,
color varchar(7) not null
);
alter table stars
add constraint color_hex_constraint
check (color ~* '^#[a-f0-9]{2}[a-f0-9]{2}[a-f0-9]{2}$');

18
src/config.rs Normal file
View File

@ -0,0 +1,18 @@
use once_cell::sync::Lazy;
use serde_derive::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct Config {
pub db_url: String,
}
pub static CONFIG: Lazy<Config> =
Lazy::new(|| confy::load("starchart", Some("config")).expect("failed to load config"));
impl Default for Config {
fn default() -> Self {
Self {
db_url: "postgres://starchart:hunter2@localhost/starchart".to_string(),
}
}
}

17
src/db.rs Normal file
View File

@ -0,0 +1,17 @@
use sqlx::{postgres::PgPoolOptions, PgPool};
use tokio::sync::OnceCell;
static DB_CONN: OnceCell<PgPool> = OnceCell::const_new();
pub async fn pool() -> &'static sqlx::postgres::PgPool {
DB_CONN
.get_or_init(|| async {
PgPoolOptions::new()
.min_connections(1)
.max_connections(5)
.connect(&crate::config::CONFIG.db_url)
.await
.expect("failed to connect to database")
})
.await
}

25
src/docs.rs Normal file
View File

@ -0,0 +1,25 @@
use std::sync::Arc;
use aide::{
axum::{routing::get, ApiRouter, IntoApiResponse},
openapi::OpenApi,
redoc::Redoc,
};
use axum::{response::IntoResponse, Extension};
use crate::extractors::Json;
pub fn routes() -> ApiRouter {
ApiRouter::new()
.route("/openapi.json", get(openapi_json))
.route(
"/",
get(Redoc::new("/docs/openapi.json")
.with_title("starchart docs")
.axum_handler()),
)
}
async fn openapi_json(Extension(api): Extension<Arc<OpenApi>>) -> impl IntoApiResponse {
Json(api).into_response()
}

21
src/extractors.rs Normal file
View File

@ -0,0 +1,21 @@
use aide::operation::OperationIo;
use axum_macros::FromRequest;
use serde::Serialize;
#[derive(OperationIo, FromRequest)]
#[from_request(via(axum_jsonschema::Json))]
#[aide(
input_with = "axum_jsonschema::Json<T>",
output_with = "axum_jsonschema::Json<T>",
json_schema
)]
pub struct Json<T>(pub T);
impl<T> axum::response::IntoResponse for Json<T>
where
T: Serialize,
{
fn into_response(self) -> axum::response::Response {
axum::Json(self.0).into_response()
}
}

45
src/frontend.rs Normal file
View File

@ -0,0 +1,45 @@
use aide::axum::ApiRouter;
use axum::{
body::{boxed, Full},
response::{Html, IntoResponse, Response},
routing::get,
};
use hyper::{header, StatusCode, Uri};
use rust_embed::RustEmbed;
pub fn routes() -> ApiRouter {
ApiRouter::new()
.route("/*file", get(static_handler))
.route("/", get(index))
}
async fn index() -> Html<&'static str> {
Html(include_str!("../web/dist/index.html"))
}
async fn static_handler(uri: Uri) -> impl IntoResponse {
let path = uri.path().trim_start_matches('/');
if path.starts_with("/api/") {
return StatusCode::NOT_FOUND.into_response();
}
match Assets::get(path) {
Some(v) => {
tracing::debug!("found file: {}", path);
let body = boxed(Full::from(v.data));
let mime = mime_guess::from_path(path).first_or_octet_stream();
Response::builder()
.header(header::CONTENT_TYPE, mime.as_ref())
.body(body)
.unwrap()
}
// return index.html, vue router will handle the rest
None => index().await.into_response(),
}
}
#[derive(RustEmbed)]
#[folder = "web/dist/"]
struct Assets;

56
src/main.rs Normal file
View File

@ -0,0 +1,56 @@
use aide::{axum::ApiRouter, openapi::OpenApi};
use axum::Extension;
use std::{
net::{Ipv4Addr, SocketAddr},
sync::Arc,
};
use tracing::info;
use tracing_subscriber::fmt::SubscriberBuilder;
pub mod config;
pub mod db;
pub mod docs;
pub mod extractors;
mod frontend;
pub mod stars;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
SubscriberBuilder::default().pretty().init();
info!("running migrations! please wait...");
sqlx::migrate!("./migrations").run(db::pool().await).await?;
info!("migrations done! 😎");
start_server().await?;
Ok(())
}
async fn generate_server() -> Result<axum::Router, Box<dyn std::error::Error>> {
aide::gen::on_error(|error| panic!("{}", error));
aide::gen::extract_schemas(true);
let mut open_api = OpenApi::default();
aide::gen::on_error(|error| panic!("{}", error));
Ok(ApiRouter::new()
.nest_api_service("/api/v1/stars", stars::routes())
.nest_api_service("/docs", docs::routes())
.nest_api_service("/", frontend::routes())
.finish_api_with(&mut open_api, |docs| {
docs.title("starchart")
.summary("now you can discover a star!")
.contact(aide::openapi::Contact {
name: Some("zoe".into()),
url: Some("https://zoe.kittycat.homes".into()),
..Default::default()
})
.version("0.1.0")
})
.layer(Extension(Arc::new(open_api))))
}
async fn start_server() -> Result<(), Box<dyn std::error::Error>> {
let app = generate_server().await?;
let addr = SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7056);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await?;
Ok(())
}

124
src/stars.rs Normal file
View File

@ -0,0 +1,124 @@
use aide::axum::{
routing::{get_with, post_with},
ApiRouter,
};
use axum::{
extract::{Path, Query},
http::StatusCode,
Json,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
mod size;
pub fn routes() -> ApiRouter {
ApiRouter::new()
.api_route(
"/discover",
post_with(discover, |docs| {
docs.response::<200, Json<Star>>()
.summary("add a new planet")
.id("discover")
.tag("stars")
}),
)
.api_route(
"/chart",
get_with(chart, |docs| {
docs.response::<200, Json<Vec<Protostar>>>()
.summary("the whole chart")
.id("chart")
.tag("stars")
}),
)
.api_route_with(
"/visit/",
get_with(visit, |docs| {
docs.response::<200, Json<Star>>()
.summary("visit a single planet")
.id("visit")
.tag("stars")
}),
|docs| docs,
)
}
/// this gets stored in the db,
/// the other stuff is generated using
/// the id as a seed
#[derive(Serialize, FromRow, Clone, Deserialize, JsonSchema)]
pub struct Protostar {
pub id: i32,
/// hex code of the stars color
pub color: String,
}
impl Protostar {
fn seed(&self) -> u64 {
self.id.abs() as u64
}
}
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct Star {
pub size: size::Size,
pub core: Protostar,
}
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct DiscoveryLog {
pub color: String,
}
impl Into<Star> for Protostar {
fn into(self) -> Star {
let seed = self.seed();
Star {
core: self,
size: size::Size::random(seed),
}
}
}
/// discover a new star in the solar system
async fn discover(Json(log): Json<DiscoveryLog>) -> Result<Json<Star>, StatusCode> {
let query = "INSERT INTO stars (color) VALUES ($1) RETURNING id, color";
let protostar: Protostar = sqlx::query_as(query)
.bind(log.color)
.fetch_one(crate::db::pool().await)
.await
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
let star: Star = protostar.into();
Ok(Json(star))
}
/// show all stars
async fn chart() -> Json<Vec<Protostar>> {
let query = "SELECT * FROM stars ORDER BY id DESC";
let protostars: Vec<Protostar> = sqlx::query_as(query)
.fetch_all(crate::db::pool().await)
.await
.unwrap_or_default();
Json(protostars)
}
#[derive(Serialize, Deserialize, JsonSchema)]
struct VisitorData {
/// the planet to visit
planet_id: i32,
}
async fn visit(data: Query<VisitorData>) -> Result<Json<Star>, StatusCode> {
let query = "SELECT * FROM stars WHERE id = ($1)";
let star: Protostar = sqlx::query_as(query)
.bind(data.planet_id)
.fetch_one(crate::db::pool().await)
.await
.unwrap();
//.or(Err(StatusCode::NOT_FOUND))?;
let star: Star = star.into();
Ok(Json(star))
}

29
src/stars/size.rs Normal file
View File

@ -0,0 +1,29 @@
use tinyrand::{RandRange, Seeded, StdRand};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(JsonSchema, Serialize, Deserialize)]
pub struct Size {
pub small_item_multiplier: u8,
pub large_item_multiplier: u8,
pub small_item: String,
pub large_item: String,
}
impl Size {
pub fn random(seed: u64) -> Self {
let mut rand = StdRand::seed(seed);
// multipliers:
// this planet is as large as 10 x item
let small_item_multiplier: usize = rand.next_range(1..100);
let large_item_multiplier: usize = rand.next_range(1..100);
Self {
small_item_multiplier: small_item_multiplier as u8,
large_item_multiplier: large_item_multiplier as u8,
small_item: "rats".into(),
large_item: "ohios".into(),
}
}
}

4
web/.browserslistrc Normal file
View File

@ -0,0 +1,4 @@
> 1%
last 2 versions
not dead
not ie 11

19
web/.eslintrc.js Normal file
View File

@ -0,0 +1,19 @@
module.exports = {
root: true,
env: {
node: true,
},
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/typescript/recommended",
"plugin:prettier/recommended",
],
parserOptions: {
ecmaVersion: 2020,
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
},
};

23
web/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

24
web/README.md Normal file
View File

@ -0,0 +1,24 @@
# starchart
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

3
web/babel.config.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
};

12573
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

36
web/package.json Normal file
View File

@ -0,0 +1,36 @@
{
"name": "starchart",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"swagger-gen": "rm -rf ./src/swagger && openapi-generator-cli generate -g typescript-fetch -i http://localhost:7056/docs/openapi.json -o src/swagger/"
},
"dependencies": {
"core-js": "^3.8.3",
"vue": "^3.2.13",
"vue-router": "^4.0.3"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-typescript": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-typescript": "^9.1.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3",
"pinia": "^2.1.6",
"prettier": "^2.4.1",
"sass": "^1.32.7",
"sass-loader": "^12.0.0",
"typescript": "~4.5.5",
"vue-router": "^4.2.4"
}
}

BIN
web/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

17
web/public/index.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

28
web/src/App.vue Normal file
View File

@ -0,0 +1,28 @@
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<router-view />
</template>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
color: #2c3e50;
}
nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>

13
web/src/api/index.ts Normal file
View File

@ -0,0 +1,13 @@
import { Configuration } from "@/swagger";
// is this production or dev?
function is_dev(): boolean {
return process.env.NODE_ENV === "development";
}
export function conf(): Configuration {
return new Configuration({
// what url to send requests to
basePath: is_dev() ? "http://127.0.0.1:7056" : window.location.origin,
});
}

BIN
web/src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,124 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br />
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
>vue-cli documentation</a
>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
target="_blank"
rel="noopener"
>babel</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript"
target="_blank"
rel="noopener"
>typescript</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
target="_blank"
rel="noopener"
>eslint</a
>
</li>
</ul>
<h3>Essential Links</h3>
<ul>
<li>
<a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
</li>
<li>
<a href="https://forum.vuejs.org" target="_blank" rel="noopener"
>Forum</a
>
</li>
<li>
<a href="https://chat.vuejs.org" target="_blank" rel="noopener"
>Community Chat</a
>
</li>
<li>
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
>Twitter</a
>
</li>
<li>
<a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
</li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li>
<a href="https://router.vuejs.org" target="_blank" rel="noopener"
>vue-router</a
>
</li>
<li>
<a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
</li>
<li>
<a
href="https://github.com/vuejs/vue-devtools#vue-devtools"
target="_blank"
rel="noopener"
>vue-devtools</a
>
</li>
<li>
<a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
>vue-loader</a
>
</li>
<li>
<a
href="https://github.com/vuejs/awesome-vue"
target="_blank"
rel="noopener"
>awesome-vue</a
>
</li>
</ul>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "HelloWorld",
props: {
msg: String,
},
});
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

11
web/src/main.ts Normal file
View File

@ -0,0 +1,11 @@
import { createApp } from "vue";
import { createPinia } from "pinia";
import "vue-router";
import App from "./App.vue";
import router from "./router";
const pinia = createPinia();
const app = createApp(App).use(router);
app.use(pinia);
app.mount("#app");

26
web/src/router/index.ts Normal file
View File

@ -0,0 +1,26 @@
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import HomeView from "../views/HomeView.vue";
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "home",
component: HomeView,
},
{
path: "/about",
name: "about",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;

6
web/src/shims-vue.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

54
web/src/state/stars.ts Normal file
View File

@ -0,0 +1,54 @@
import { conf } from "@/api";
import { Protostar, StarsApi } from "@/swagger";
import { defineStore } from "pinia";
type ChartInfoState = {
loading: boolean;
protostars: Protostar[];
};
export const useChartStore = defineStore({
id: "chart",
state: () =>
({
loading: true,
protostars: [],
} as ChartInfoState),
actions: {
async fetchChart() {
this.loading = true;
this.protostars = await new StarsApi(conf())
.chart()
.catch((e) => {
console.error(e);
return this.protostars;
})
.finally(() => {
this.loading = false;
});
},
},
});
export const useVisitStore = defineStore({
id: "chart",
state: () =>
({
loading: true,
protostars: [],
} as ChartInfoState),
actions: {
async fetchChart() {
this.loading = true;
this.protostars = await new StarsApi(conf())
.chart()
.catch((e) => {
console.error(e);
return this.protostars;
})
.finally(() => {
this.loading = false;
});
},
},
});

View File

@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@ -0,0 +1,11 @@
.openapi-generator-ignore
apis/StarsApi.ts
apis/index.ts
index.ts
models/DiscoveryLog.ts
models/Protostar.ts
models/Size.ts
models/Star.ts
models/VisitorData.ts
models/index.ts
runtime.ts

View File

@ -0,0 +1 @@
6.6.0

View File

@ -0,0 +1,138 @@
/* tslint:disable */
/* eslint-disable */
/**
* starchart
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import * as runtime from '../runtime';
import type {
DiscoveryLog,
Star,
} from '../models';
import {
DiscoveryLogFromJSON,
DiscoveryLogToJSON,
StarFromJSON,
StarToJSON,
} from '../models';
export interface DiscoverRequest {
discoveryLog: DiscoveryLog;
}
export interface VisitRequest {
planetId: any;
}
/**
*
*/
export class StarsApi extends runtime.BaseAPI {
/**
* the whole chart
*/
async chartRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<any>> {
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
const response = await this.request({
path: `/api/v1/stars/chart`,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
if (this.isJsonMime(response.headers.get('content-type'))) {
return new runtime.JSONApiResponse<any>(response);
} else {
return new runtime.TextApiResponse(response) as any;
}
}
/**
* the whole chart
*/
async chart(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<any> {
const response = await this.chartRaw(initOverrides);
return await response.value();
}
/**
* add a new planet
*/
async discoverRaw(requestParameters: DiscoverRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Star>> {
if (requestParameters.discoveryLog === null || requestParameters.discoveryLog === undefined) {
throw new runtime.RequiredError('discoveryLog','Required parameter requestParameters.discoveryLog was null or undefined when calling discover.');
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
const response = await this.request({
path: `/api/v1/stars/discover`,
method: 'POST',
headers: headerParameters,
query: queryParameters,
body: DiscoveryLogToJSON(requestParameters.discoveryLog),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => StarFromJSON(jsonValue));
}
/**
* add a new planet
*/
async discover(requestParameters: DiscoverRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Star> {
const response = await this.discoverRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* visit a single planet
*/
async visitRaw(requestParameters: VisitRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Star>> {
if (requestParameters.planetId === null || requestParameters.planetId === undefined) {
throw new runtime.RequiredError('planetId','Required parameter requestParameters.planetId was null or undefined when calling visit.');
}
const queryParameters: any = {};
if (requestParameters.planetId !== undefined) {
queryParameters['planet_id'] = requestParameters.planetId;
}
const headerParameters: runtime.HTTPHeaders = {};
const response = await this.request({
path: `/api/v1/stars/visit/`,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => StarFromJSON(jsonValue));
}
/**
* visit a single planet
*/
async visit(requestParameters: VisitRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Star> {
const response = await this.visitRaw(requestParameters, initOverrides);
return await response.value();
}
}

View File

@ -0,0 +1,3 @@
/* tslint:disable */
/* eslint-disable */
export * from './StarsApi';

5
web/src/swagger/index.ts Normal file
View File

@ -0,0 +1,5 @@
/* tslint:disable */
/* eslint-disable */
export * from './runtime';
export * from './apis';
export * from './models';

View File

@ -0,0 +1,66 @@
/* tslint:disable */
/* eslint-disable */
/**
* starchart
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
/**
*
* @export
* @interface DiscoveryLog
*/
export interface DiscoveryLog {
/**
*
* @type {any}
* @memberof DiscoveryLog
*/
color: any | null;
}
/**
* Check if a given object implements the DiscoveryLog interface.
*/
export function instanceOfDiscoveryLog(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "color" in value;
return isInstance;
}
export function DiscoveryLogFromJSON(json: any): DiscoveryLog {
return DiscoveryLogFromJSONTyped(json, false);
}
export function DiscoveryLogFromJSONTyped(json: any, ignoreDiscriminator: boolean): DiscoveryLog {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'color': json['color'],
};
}
export function DiscoveryLogToJSON(value?: DiscoveryLog | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'color': value.color,
};
}

View File

@ -0,0 +1,75 @@
/* tslint:disable */
/* eslint-disable */
/**
* starchart
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
/**
* this gets stored in the db, the other stuff is generated using the id as a seed
* @export
* @interface Protostar
*/
export interface Protostar {
/**
* hex code of the stars color
* @type {any}
* @memberof Protostar
*/
color: any | null;
/**
*
* @type {any}
* @memberof Protostar
*/
id: any | null;
}
/**
* Check if a given object implements the Protostar interface.
*/
export function instanceOfProtostar(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "color" in value;
isInstance = isInstance && "id" in value;
return isInstance;
}
export function ProtostarFromJSON(json: any): Protostar {
return ProtostarFromJSONTyped(json, false);
}
export function ProtostarFromJSONTyped(json: any, ignoreDiscriminator: boolean): Protostar {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'color': json['color'],
'id': json['id'],
};
}
export function ProtostarToJSON(value?: Protostar | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'color': value.color,
'id': value.id,
};
}

View File

@ -0,0 +1,93 @@
/* tslint:disable */
/* eslint-disable */
/**
* starchart
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
/**
*
* @export
* @interface Size
*/
export interface Size {
/**
*
* @type {any}
* @memberof Size
*/
largeItem: any | null;
/**
*
* @type {any}
* @memberof Size
*/
largeItemMultiplier: any | null;
/**
*
* @type {any}
* @memberof Size
*/
smallItem: any | null;
/**
*
* @type {any}
* @memberof Size
*/
smallItemMultiplier: any | null;
}
/**
* Check if a given object implements the Size interface.
*/
export function instanceOfSize(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "largeItem" in value;
isInstance = isInstance && "largeItemMultiplier" in value;
isInstance = isInstance && "smallItem" in value;
isInstance = isInstance && "smallItemMultiplier" in value;
return isInstance;
}
export function SizeFromJSON(json: any): Size {
return SizeFromJSONTyped(json, false);
}
export function SizeFromJSONTyped(json: any, ignoreDiscriminator: boolean): Size {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'largeItem': json['large_item'],
'largeItemMultiplier': json['large_item_multiplier'],
'smallItem': json['small_item'],
'smallItemMultiplier': json['small_item_multiplier'],
};
}
export function SizeToJSON(value?: Size | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'large_item': value.largeItem,
'large_item_multiplier': value.largeItemMultiplier,
'small_item': value.smallItem,
'small_item_multiplier': value.smallItemMultiplier,
};
}

View File

@ -0,0 +1,88 @@
/* tslint:disable */
/* eslint-disable */
/**
* starchart
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
import type { Protostar } from './Protostar';
import {
ProtostarFromJSON,
ProtostarFromJSONTyped,
ProtostarToJSON,
} from './Protostar';
import type { Size } from './Size';
import {
SizeFromJSON,
SizeFromJSONTyped,
SizeToJSON,
} from './Size';
/**
*
* @export
* @interface Star
*/
export interface Star {
/**
*
* @type {Protostar}
* @memberof Star
*/
core: Protostar;
/**
*
* @type {Size}
* @memberof Star
*/
size: Size;
}
/**
* Check if a given object implements the Star interface.
*/
export function instanceOfStar(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "core" in value;
isInstance = isInstance && "size" in value;
return isInstance;
}
export function StarFromJSON(json: any): Star {
return StarFromJSONTyped(json, false);
}
export function StarFromJSONTyped(json: any, ignoreDiscriminator: boolean): Star {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'core': ProtostarFromJSON(json['core']),
'size': SizeFromJSON(json['size']),
};
}
export function StarToJSON(value?: Star | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'core': ProtostarToJSON(value.core),
'size': SizeToJSON(value.size),
};
}

View File

@ -0,0 +1,66 @@
/* tslint:disable */
/* eslint-disable */
/**
* starchart
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
/**
*
* @export
* @interface VisitorData
*/
export interface VisitorData {
/**
* the planet to visit
* @type {any}
* @memberof VisitorData
*/
planetId: any | null;
}
/**
* Check if a given object implements the VisitorData interface.
*/
export function instanceOfVisitorData(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "planetId" in value;
return isInstance;
}
export function VisitorDataFromJSON(json: any): VisitorData {
return VisitorDataFromJSONTyped(json, false);
}
export function VisitorDataFromJSONTyped(json: any, ignoreDiscriminator: boolean): VisitorData {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'planetId': json['planet_id'],
};
}
export function VisitorDataToJSON(value?: VisitorData | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'planet_id': value.planetId,
};
}

View File

@ -0,0 +1,7 @@
/* tslint:disable */
/* eslint-disable */
export * from './DiscoveryLog';
export * from './Protostar';
export * from './Size';
export * from './Star';
export * from './VisitorData';

425
web/src/swagger/runtime.ts Normal file
View File

@ -0,0 +1,425 @@
/* tslint:disable */
/* eslint-disable */
/**
* starchart
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export const BASE_PATH = "http://localhost".replace(/\/+$/, "");
export interface ConfigurationParameters {
basePath?: string; // override base path
fetchApi?: FetchAPI; // override for fetch implementation
middleware?: Middleware[]; // middleware to apply before/after fetch requests
queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings
username?: string; // parameter for basic security
password?: string; // parameter for basic security
apiKey?: string | ((name: string) => string); // parameter for apiKey security
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string | Promise<string>); // parameter for oauth2 security
headers?: HTTPHeaders; //header params we want to use on every request
credentials?: RequestCredentials; //value for the credentials param we want to use on each request
}
export class Configuration {
constructor(private configuration: ConfigurationParameters = {}) {}
set config(configuration: Configuration) {
this.configuration = configuration;
}
get basePath(): string {
return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH;
}
get fetchApi(): FetchAPI | undefined {
return this.configuration.fetchApi;
}
get middleware(): Middleware[] {
return this.configuration.middleware || [];
}
get queryParamsStringify(): (params: HTTPQuery) => string {
return this.configuration.queryParamsStringify || querystring;
}
get username(): string | undefined {
return this.configuration.username;
}
get password(): string | undefined {
return this.configuration.password;
}
get apiKey(): ((name: string) => string) | undefined {
const apiKey = this.configuration.apiKey;
if (apiKey) {
return typeof apiKey === 'function' ? apiKey : () => apiKey;
}
return undefined;
}
get accessToken(): ((name?: string, scopes?: string[]) => string | Promise<string>) | undefined {
const accessToken = this.configuration.accessToken;
if (accessToken) {
return typeof accessToken === 'function' ? accessToken : async () => accessToken;
}
return undefined;
}
get headers(): HTTPHeaders | undefined {
return this.configuration.headers;
}
get credentials(): RequestCredentials | undefined {
return this.configuration.credentials;
}
}
export const DefaultConfig = new Configuration();
/**
* This is the base class for all generated API classes.
*/
export class BaseAPI {
private static readonly jsonRegex = new RegExp('^(:?application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i');
private middleware: Middleware[];
constructor(protected configuration = DefaultConfig) {
this.middleware = configuration.middleware;
}
withMiddleware<T extends BaseAPI>(this: T, ...middlewares: Middleware[]) {
const next = this.clone<T>();
next.middleware = next.middleware.concat(...middlewares);
return next;
}
withPreMiddleware<T extends BaseAPI>(this: T, ...preMiddlewares: Array<Middleware['pre']>) {
const middlewares = preMiddlewares.map((pre) => ({ pre }));
return this.withMiddleware<T>(...middlewares);
}
withPostMiddleware<T extends BaseAPI>(this: T, ...postMiddlewares: Array<Middleware['post']>) {
const middlewares = postMiddlewares.map((post) => ({ post }));
return this.withMiddleware<T>(...middlewares);
}
/**
* Check if the given MIME is a JSON MIME.
* JSON MIME examples:
* application/json
* application/json; charset=UTF8
* APPLICATION/JSON
* application/vnd.company+json
* @param mime - MIME (Multipurpose Internet Mail Extensions)
* @return True if the given MIME is JSON, false otherwise.
*/
protected isJsonMime(mime: string | null | undefined): boolean {
if (!mime) {
return false;
}
return BaseAPI.jsonRegex.test(mime);
}
protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise<Response> {
const { url, init } = await this.createFetchParams(context, initOverrides);
const response = await this.fetchApi(url, init);
if (response && (response.status >= 200 && response.status < 300)) {
return response;
}
throw new ResponseError(response, 'Response returned an error code');
}
private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) {
let url = this.configuration.basePath + context.path;
if (context.query !== undefined && Object.keys(context.query).length !== 0) {
// only add the querystring to the URL if there are query parameters.
// this is done to avoid urls ending with a "?" character which buggy webservers
// do not handle correctly sometimes.
url += '?' + this.configuration.queryParamsStringify(context.query);
}
const headers = Object.assign({}, this.configuration.headers, context.headers);
Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {});
const initOverrideFn =
typeof initOverrides === "function"
? initOverrides
: async () => initOverrides;
const initParams = {
method: context.method,
headers,
body: context.body,
credentials: this.configuration.credentials,
};
const overriddenInit: RequestInit = {
...initParams,
...(await initOverrideFn({
init: initParams,
context,
}))
};
const init: RequestInit = {
...overriddenInit,
body:
isFormData(overriddenInit.body) ||
overriddenInit.body instanceof URLSearchParams ||
isBlob(overriddenInit.body)
? overriddenInit.body
: JSON.stringify(overriddenInit.body),
};
return { url, init };
}
private fetchApi = async (url: string, init: RequestInit) => {
let fetchParams = { url, init };
for (const middleware of this.middleware) {
if (middleware.pre) {
fetchParams = await middleware.pre({
fetch: this.fetchApi,
...fetchParams,
}) || fetchParams;
}
}
let response: Response | undefined = undefined;
try {
response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init);
} catch (e) {
for (const middleware of this.middleware) {
if (middleware.onError) {
response = await middleware.onError({
fetch: this.fetchApi,
url: fetchParams.url,
init: fetchParams.init,
error: e,
response: response ? response.clone() : undefined,
}) || response;
}
}
if (response === undefined) {
if (e instanceof Error) {
throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response');
} else {
throw e;
}
}
}
for (const middleware of this.middleware) {
if (middleware.post) {
response = await middleware.post({
fetch: this.fetchApi,
url: fetchParams.url,
init: fetchParams.init,
response: response.clone(),
}) || response;
}
}
return response;
}
/**
* Create a shallow clone of `this` by constructing a new instance
* and then shallow cloning data members.
*/
private clone<T extends BaseAPI>(this: T): T {
const constructor = this.constructor as any;
const next = new constructor(this.configuration);
next.middleware = this.middleware.slice();
return next;
}
};
function isBlob(value: any): value is Blob {
return typeof Blob !== 'undefined' && value instanceof Blob;
}
function isFormData(value: any): value is FormData {
return typeof FormData !== "undefined" && value instanceof FormData;
}
export class ResponseError extends Error {
override name: "ResponseError" = "ResponseError";
constructor(public response: Response, msg?: string) {
super(msg);
}
}
export class FetchError extends Error {
override name: "FetchError" = "FetchError";
constructor(public cause: Error, msg?: string) {
super(msg);
}
}
export class RequiredError extends Error {
override name: "RequiredError" = "RequiredError";
constructor(public field: string, msg?: string) {
super(msg);
}
}
export const COLLECTION_FORMATS = {
csv: ",",
ssv: " ",
tsv: "\t",
pipes: "|",
};
export type FetchAPI = WindowOrWorkerGlobalScope['fetch'];
export type Json = any;
export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
export type HTTPHeaders = { [key: string]: string };
export type HTTPQuery = { [key: string]: string | number | null | boolean | Array<string | number | null | boolean> | Set<string | number | null | boolean> | HTTPQuery };
export type HTTPBody = Json | FormData | URLSearchParams;
export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody };
export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original';
export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise<RequestInit>
export interface FetchParams {
url: string;
init: RequestInit;
}
export interface RequestOpts {
path: string;
method: HTTPMethod;
headers: HTTPHeaders;
query?: HTTPQuery;
body?: HTTPBody;
}
export function exists(json: any, key: string) {
const value = json[key];
return value !== null && value !== undefined;
}
export function querystring(params: HTTPQuery, prefix: string = ''): string {
return Object.keys(params)
.map(key => querystringSingleKey(key, params[key], prefix))
.filter(part => part.length > 0)
.join('&');
}
function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array<string | number | null | boolean> | Set<string | number | null | boolean> | HTTPQuery, keyPrefix: string = ''): string {
const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key);
if (value instanceof Array) {
const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue)))
.join(`&${encodeURIComponent(fullKey)}=`);
return `${encodeURIComponent(fullKey)}=${multiValue}`;
}
if (value instanceof Set) {
const valueAsArray = Array.from(value);
return querystringSingleKey(key, valueAsArray, keyPrefix);
}
if (value instanceof Date) {
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`;
}
if (value instanceof Object) {
return querystring(value as HTTPQuery, fullKey);
}
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`;
}
export function mapValues(data: any, fn: (item: any) => any) {
return Object.keys(data).reduce(
(acc, key) => ({ ...acc, [key]: fn(data[key]) }),
{}
);
}
export function canConsumeForm(consumes: Consume[]): boolean {
for (const consume of consumes) {
if ('multipart/form-data' === consume.contentType) {
return true;
}
}
return false;
}
export interface Consume {
contentType: string;
}
export interface RequestContext {
fetch: FetchAPI;
url: string;
init: RequestInit;
}
export interface ResponseContext {
fetch: FetchAPI;
url: string;
init: RequestInit;
response: Response;
}
export interface ErrorContext {
fetch: FetchAPI;
url: string;
init: RequestInit;
error: unknown;
response?: Response;
}
export interface Middleware {
pre?(context: RequestContext): Promise<FetchParams | void>;
post?(context: ResponseContext): Promise<Response | void>;
onError?(context: ErrorContext): Promise<Response | void>;
}
export interface ApiResponse<T> {
raw: Response;
value(): Promise<T>;
}
export interface ResponseTransformer<T> {
(json: any): T;
}
export class JSONApiResponse<T> {
constructor(public raw: Response, private transformer: ResponseTransformer<T> = (jsonValue: any) => jsonValue) {}
async value(): Promise<T> {
return this.transformer(await this.raw.json());
}
}
export class VoidApiResponse {
constructor(public raw: Response) {}
async value(): Promise<void> {
return undefined;
}
}
export class BlobApiResponse {
constructor(public raw: Response) {}
async value(): Promise<Blob> {
return await this.raw.blob();
};
}
export class TextApiResponse {
constructor(public raw: Response) {}
async value(): Promise<string> {
return await this.raw.text();
};
}

View File

@ -0,0 +1,5 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View File

@ -0,0 +1,18 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js App" />
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from "@/components/HelloWorld.vue";
export default {
name: "HomeView",
components: {
HelloWorld,
},
};
</script>

40
web/tsconfig.json Normal file
View File

@ -0,0 +1,40 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"moduleResolution": "node",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

4
web/vue.config.js Normal file
View File

@ -0,0 +1,4 @@
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
transpileDependencies: true,
});