23 changed files with 103 additions and 462 deletions
			
			
		- 
					4api/items.go
 - 
					4api/projects.go
 - 
					6api/scope.go
 - 
					4api/stats.go
 - 
					1frontend/src/app.d.ts
 - 
					7frontend/src/hooks.ts
 - 
					2frontend/src/lib/components/frontpage/ScopeLink.svelte
 - 
					17frontend/src/lib/config.ts
 - 
					37frontend/src/lib/database/interfaces.ts
 - 
					48frontend/src/lib/database/mysql/database.ts
 - 
					84frontend/src/lib/database/mysql/scopes.ts
 - 
					10frontend/src/lib/models/item.ts
 - 
					7frontend/src/lib/models/project.ts
 - 
					14frontend/src/lib/models/scope.ts
 - 
					32frontend/src/lib/models/stat.ts
 - 
					13frontend/src/lib/utils/slugify.ts
 - 
					31frontend/src/routes/[scope].json.ts
 - 
					29frontend/src/routes/[scope]/__layout.svelte
 - 
					30frontend/src/routes/api/[...any]/index.ts
 - 
					171frontend/src/routes/indexdata.json.ts
 - 
					4internal/database/mysql/scopes.go
 - 
					2internal/models/item.go
 - 
					8internal/models/scope.go
 
@ -1,18 +1,3 @@ | 
			
		|||||
import type { Database } from "./database/interfaces"; | 
				 | 
			
		||||
import MysqlDB from "./database/mysql/database"; | 
				 | 
			
		||||
 | 
				 | 
			
		||||
let databasePromise: Promise<Database> | null = null; | 
				 | 
			
		||||
let databaseTime: number = 0; | 
				 | 
			
		||||
 | 
				 | 
			
		||||
const config = { | 
				 | 
			
		||||
  database() { | 
				 | 
			
		||||
    if (databasePromise == null || Date.now() < (databaseTime - 60000)) { | 
				 | 
			
		||||
      databasePromise = MysqlDB.connectEnv(); | 
				 | 
			
		||||
      databaseTime = Date.now(); | 
				 | 
			
		||||
    } | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    return databasePromise; | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
}; | 
				 | 
			
		||||
 | 
				const config = {}; | 
			
		||||
 | 
				
 | 
			
		||||
export default config; | 
				export default config; | 
			
		||||
@ -1,37 +0,0 @@ | 
			
		|||||
import type { ScopeEntry, ScopeInput } from "$lib/models/scope"; | 
				 | 
			
		||||
import type Scope from "$lib/models/scope"; | 
				 | 
			
		||||
import type { StatEntry } from "$lib/models/stat"; | 
				 | 
			
		||||
import type Stat from "$lib/models/stat"; | 
				 | 
			
		||||
 | 
				 | 
			
		||||
export class NotFoundError extends Error { | 
				 | 
			
		||||
  constructor(subject: string) { | 
				 | 
			
		||||
    super(`${subject} not found`); | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
} | 
				 | 
			
		||||
 | 
				 | 
			
		||||
export interface Database { | 
				 | 
			
		||||
  userId: string | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  scopes(): ScopeRepo | 
				 | 
			
		||||
  stats(scopeId: number): StatRepo | 
				 | 
			
		||||
  withUser(userId: string): Database | 
				 | 
			
		||||
} | 
				 | 
			
		||||
 | 
				 | 
			
		||||
export interface ScopeRepo { | 
				 | 
			
		||||
  userId: string | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  find(id: number): Promise<Scope> | 
				 | 
			
		||||
  list(): Promise<ScopeEntry[]> | 
				 | 
			
		||||
  create(input: ScopeInput): Promise<Scope> | 
				 | 
			
		||||
  update(id: number, input: Partial<ScopeInput>): Promise<Scope> | 
				 | 
			
		||||
  delete(id: number): Promise<void> | 
				 | 
			
		||||
} | 
				 | 
			
		||||
 | 
				 | 
			
		||||
export interface StatRepo { | 
				 | 
			
		||||
  userId: string | 
				 | 
			
		||||
  scopeId: number | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  find(id: number): Promise<Stat> | 
				 | 
			
		||||
  findEntries(...ids: number[]): Promise<StatEntry[]> | 
				 | 
			
		||||
  list(): Promise<Stat[]> | 
				 | 
			
		||||
} | 
				 | 
			
		||||
@ -1,48 +0,0 @@ | 
			
		|||||
import {createPool} from "mysql2/promise" | 
				 | 
			
		||||
import type {Pool} from "mysql2/promise"; | 
				 | 
			
		||||
 | 
				 | 
			
		||||
import type { Database, ScopeRepo, StatRepo } from "../interfaces"; | 
				 | 
			
		||||
import MysqlDBScopes from "./scopes"; | 
				 | 
			
		||||
 | 
				 | 
			
		||||
 | 
				 | 
			
		||||
export default class MysqlDB implements Database { | 
				 | 
			
		||||
  connection: Pool | 
				 | 
			
		||||
  userId: string; | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  private constructor(userId: string, connection: Pool) { | 
				 | 
			
		||||
    this.userId = userId; | 
				 | 
			
		||||
    this.connection = connection; | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  scopes(): ScopeRepo { | 
				 | 
			
		||||
    return new MysqlDBScopes(this.connection, this.userId); | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
  stats(scopeId: number): StatRepo { | 
				 | 
			
		||||
    throw new Error("Method not implemented."); | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  withUser(userId: string): Database { | 
				 | 
			
		||||
    return new MysqlDB(userId, this.connection); | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  static async connectEnv(): Promise<MysqlDB> { | 
				 | 
			
		||||
    return this.connect( | 
				 | 
			
		||||
      process.env.STUFFLOG3_MYSQL_HOST, | 
				 | 
			
		||||
      parseInt(process.env.STUFFLOG3_MYSQL_PORT), | 
				 | 
			
		||||
      process.env.STUFFLOG3_MYSQL_USERNAME, | 
				 | 
			
		||||
      process.env.STUFFLOG3_MYSQL_PASSWORD, | 
				 | 
			
		||||
      process.env.STUFFLOG3_MYSQL_SCHEMA, | 
				 | 
			
		||||
    ) | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  static async connect(host: string, port: number, user: string, password: string, database: string): Promise<MysqlDB> { | 
				 | 
			
		||||
    const connection = await createPool({ | 
				 | 
			
		||||
      host, user, database, password, port, | 
				 | 
			
		||||
      waitForConnections: true, | 
				 | 
			
		||||
      connectionLimit: 20, | 
				 | 
			
		||||
      queueLimit: 0, | 
				 | 
			
		||||
    }); | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    return new MysqlDB("", connection); | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
} | 
				 | 
			
		||||
@ -1,84 +0,0 @@ | 
			
		|||||
import type {Pool} from "mysql2/promise"; | 
				 | 
			
		||||
 | 
				 | 
			
		||||
import type Scope from "$lib/models/scope"; | 
				 | 
			
		||||
import type { ScopeEntry, ScopeInput } from "$lib/models/scope"; | 
				 | 
			
		||||
import type { ScopeRepo } from "../interfaces"; | 
				 | 
			
		||||
 | 
				 | 
			
		||||
export default class MysqlDBScopes implements ScopeRepo { | 
				 | 
			
		||||
  userId: string; | 
				 | 
			
		||||
  connection: Pool; | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  constructor(connection: Pool, userId: string) { | 
				 | 
			
		||||
    this.connection = connection; | 
				 | 
			
		||||
    this.userId = userId; | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  async find(id: number): Promise<Scope> { | 
				 | 
			
		||||
    const [[scopeRows], [projectRows], [statRows]] = await Promise.all([ | 
				 | 
			
		||||
      this.connection.execute(`
 | 
				 | 
			
		||||
        SELECT scope.*, scope_member.name as display_name  | 
				 | 
			
		||||
        FROM scope  | 
				 | 
			
		||||
        INNER JOIN scope_member ON scope.id = scope_member.scope_id | 
				 | 
			
		||||
        WHERE scope.id = ? | 
				 | 
			
		||||
      `, [id]),
 | 
				 | 
			
		||||
      this.connection.execute(`
 | 
				 | 
			
		||||
        SELECT id,name,status | 
				 | 
			
		||||
        FROM project  | 
				 | 
			
		||||
        WHERE scope_id = ? | 
				 | 
			
		||||
      `, [id]).catch(() => []),
 | 
				 | 
			
		||||
      this.connection.execute(`
 | 
				 | 
			
		||||
        SELECT *  | 
				 | 
			
		||||
        FROM stats  | 
				 | 
			
		||||
        WHERE scope_id = ? | 
				 | 
			
		||||
      `, [id]).catch(() => []),
 | 
				 | 
			
		||||
    ]); | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    const r = scopeRows[0] as any; | 
				 | 
			
		||||
    if (!r) { | 
				 | 
			
		||||
      return null; | 
				 | 
			
		||||
    } | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    return { | 
				 | 
			
		||||
      id: r.id, | 
				 | 
			
		||||
      name: r.name, | 
				 | 
			
		||||
      abbreviation: r.abbreviation, | 
				 | 
			
		||||
      displayName: r.display_name, | 
				 | 
			
		||||
      projects: ((projectRows||[]) as any[]).map((p:any) => ({ | 
				 | 
			
		||||
        id: p.id, | 
				 | 
			
		||||
        name: p.name, | 
				 | 
			
		||||
        status: p.status, | 
				 | 
			
		||||
      })), | 
				 | 
			
		||||
      stats: ((statRows||[]) as any[]).map((s:any) => ({ | 
				 | 
			
		||||
        id: s.id, | 
				 | 
			
		||||
        name: s.name, | 
				 | 
			
		||||
        weight: s.weight, | 
				 | 
			
		||||
        description: s.description, | 
				 | 
			
		||||
        allowedAmounts: s.allowed_amounts || void(0), | 
				 | 
			
		||||
      })), | 
				 | 
			
		||||
    } | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
  async list(): Promise<ScopeEntry[]> { | 
				 | 
			
		||||
    const [rows] = await this.connection.execute(`
 | 
				 | 
			
		||||
      SELECT scope.*, scope_member.name as display_name  | 
				 | 
			
		||||
      FROM scope  | 
				 | 
			
		||||
      INNER JOIN scope_member ON id = scope_id | 
				 | 
			
		||||
      WHERE user_id = ? | 
				 | 
			
		||||
    `, [this.userId]);
 | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    return (rows as any[]).map(r => ({ | 
				 | 
			
		||||
      id: r.id, | 
				 | 
			
		||||
      name: r.name, | 
				 | 
			
		||||
      abbreviation: r.abbreviation, | 
				 | 
			
		||||
      displayName: r.display_name, | 
				 | 
			
		||||
    })) | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
  create(input: ScopeInput): Promise<Scope> { | 
				 | 
			
		||||
    throw new Error("Method not implemented."); | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
  update(id: number, input: Partial<ScopeInput>): Promise<Scope> { | 
				 | 
			
		||||
    throw new Error("Method not implemented."); | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
  delete(id: number): Promise<void> { | 
				 | 
			
		||||
    throw new Error("Method not implemented."); | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
} | 
				 | 
			
		||||
@ -0,0 +1,13 @@ | 
			
		|||||
 | 
				export default function sluggify(s: string): string { | 
			
		||||
 | 
				  let out = ""; | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				  for (const c of s.toLocaleLowerCase()) { | 
			
		||||
 | 
				    if (c >= 'a' && c <= 'z') { | 
			
		||||
 | 
				      out += c; | 
			
		||||
 | 
				    } else if (!":'".includes(c)) { | 
			
		||||
 | 
				      out += "-"; | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				  } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				  return out; | 
			
		||||
 | 
				} | 
			
		||||
@ -1,31 +0,0 @@ | 
			
		|||||
import type { RequestHandler } from "@sveltejs/kit"; | 
				 | 
			
		||||
 | 
				 | 
			
		||||
import config from "$lib/config"; | 
				 | 
			
		||||
 | 
				 | 
			
		||||
export const get: RequestHandler = async({params, locals}) => { | 
				 | 
			
		||||
  const scopeId = parseInt(params.scope); | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  const db = await config.database(); | 
				 | 
			
		||||
  const scope = await db.withUser(locals.user.id).scopes().find(scopeId) | 
				 | 
			
		||||
  if (scope === null) { | 
				 | 
			
		||||
    return { | 
				 | 
			
		||||
      status: 404, | 
				 | 
			
		||||
      headers: { | 
				 | 
			
		||||
        "Content-Type": "application/json" | 
				 | 
			
		||||
      }, | 
				 | 
			
		||||
      body: JSON.stringify({ | 
				 | 
			
		||||
        message: "Scope not found", | 
				 | 
			
		||||
      }), | 
				 | 
			
		||||
    } | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  return { | 
				 | 
			
		||||
    status: 200, | 
				 | 
			
		||||
    headers: { | 
				 | 
			
		||||
      "Content-Type": "application/json" | 
				 | 
			
		||||
    }, | 
				 | 
			
		||||
    body: JSON.stringify({ | 
				 | 
			
		||||
      scope | 
				 | 
			
		||||
    }), | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
} | 
				 | 
			
		||||
@ -0,0 +1,30 @@ | 
			
		|||||
 | 
				import type { RequestHandler } from "@sveltejs/kit"; | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const get: RequestHandler = async({ request, params }) => {   | 
			
		||||
 | 
				  const proxyUrl = `${process.env.STUFFLOG3_API}/api/${params.any}`; | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				  console.log("PRX", proxyUrl); | 
			
		||||
 | 
				  const res = await fetch(proxyUrl, { | 
			
		||||
 | 
				    method: request.method, | 
			
		||||
 | 
				    headers: { | 
			
		||||
 | 
				      ...request.headers, | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    body: request.body, | 
			
		||||
 | 
				  }); | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				  const result = { | 
			
		||||
 | 
				    status: res.status, | 
			
		||||
 | 
				    body: await res.text(), | 
			
		||||
 | 
				    headers: {}, | 
			
		||||
 | 
				  } | 
			
		||||
 | 
				  res.headers.forEach((v, k) => { | 
			
		||||
 | 
				    result.headers[k] = v; | 
			
		||||
 | 
				  }) | 
			
		||||
 | 
				  | 
			
		||||
 | 
				  return result; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const options = get; | 
			
		||||
 | 
				export const post = get; | 
			
		||||
 | 
				export const put = get; | 
			
		||||
 | 
				export const del = get; | 
			
		||||
						Write
						Preview
					
					
					Loading…
					
					Cancel
						Save
					
		Reference in new issue