integrate api into frontend.
This commit is contained in:
+2
-2
@@ -12,7 +12,7 @@ import (
|
|||||||
func Items(g *gin.RouterGroup, db database.Database) {
|
func Items(g *gin.RouterGroup, db database.Database) {
|
||||||
g.Use(scopeIDMiddleware(db))
|
g.Use(scopeIDMiddleware(db))
|
||||||
|
|
||||||
g.GET("/", handler("items", func(c *gin.Context) (res interface{}, err error) {
|
g.GET("", handler("items", func(c *gin.Context) (res interface{}, err error) {
|
||||||
mode := c.Query("mode")
|
mode := c.Query("mode")
|
||||||
|
|
||||||
var fromTime, toTime time.Time
|
var fromTime, toTime time.Time
|
||||||
@@ -79,7 +79,7 @@ func Items(g *gin.RouterGroup, db database.Database) {
|
|||||||
return db.Items(getScope(c).ID).Find(c.Request.Context(), id)
|
return db.Items(getScope(c).ID).Find(c.Request.Context(), id)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
g.POST("/", handler("item", func(c *gin.Context) (interface{}, error) {
|
g.POST("", handler("item", func(c *gin.Context) (interface{}, error) {
|
||||||
item := &models.Item{}
|
item := &models.Item{}
|
||||||
err := c.BindJSON(item)
|
err := c.BindJSON(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+2
-2
@@ -12,7 +12,7 @@ import (
|
|||||||
func Projects(g *gin.RouterGroup, db database.Database) {
|
func Projects(g *gin.RouterGroup, db database.Database) {
|
||||||
g.Use(scopeIDMiddleware(db))
|
g.Use(scopeIDMiddleware(db))
|
||||||
|
|
||||||
g.GET("/", handler("projects", func(c *gin.Context) (interface{}, error) {
|
g.GET("", handler("projects", func(c *gin.Context) (interface{}, error) {
|
||||||
return db.Projects(getScope(c).ID).List(c.Request.Context())
|
return db.Projects(getScope(c).ID).List(c.Request.Context())
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ func Projects(g *gin.RouterGroup, db database.Database) {
|
|||||||
return db.Projects(getScope(c).ID).Find(c.Request.Context(), id)
|
return db.Projects(getScope(c).ID).Find(c.Request.Context(), id)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
g.POST("/", handler("project", func(c *gin.Context) (interface{}, error) {
|
g.POST("", handler("project", func(c *gin.Context) (interface{}, error) {
|
||||||
project := &models.Project{}
|
project := &models.Project{}
|
||||||
err := c.BindJSON(project)
|
err := c.BindJSON(project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+4
-2
@@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Scopes(g *gin.RouterGroup, db database.Database) {
|
func Scopes(g *gin.RouterGroup, db database.Database) {
|
||||||
g.GET("/", handler("scopes", func(c *gin.Context) (interface{}, error) {
|
g.GET("", handler("scopes", func(c *gin.Context) (interface{}, error) {
|
||||||
return db.Scopes().ListByUser(c.Request.Context(), auth.UserID(c))
|
return db.Scopes().ListByUser(c.Request.Context(), auth.UserID(c))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -23,9 +23,11 @@ func Scopes(g *gin.RouterGroup, db database.Database) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !scope.HasMember(auth.UserID(c)) {
|
member := scope.Member(auth.UserID(c))
|
||||||
|
if member == nil {
|
||||||
return nil, slerrors.NotFound("Scope")
|
return nil, slerrors.NotFound("Scope")
|
||||||
}
|
}
|
||||||
|
scope.DisplayName = member.Name
|
||||||
scope.StatusLabels = models.StatusLabels
|
scope.StatusLabels = models.StatusLabels
|
||||||
|
|
||||||
return scope, nil
|
return scope, nil
|
||||||
|
|||||||
+2
-2
@@ -10,7 +10,7 @@ import (
|
|||||||
func Stats(g *gin.RouterGroup, db database.Database) {
|
func Stats(g *gin.RouterGroup, db database.Database) {
|
||||||
g.Use(scopeIDMiddleware(db))
|
g.Use(scopeIDMiddleware(db))
|
||||||
|
|
||||||
g.GET("/", handler("stats", func(c *gin.Context) (interface{}, error) {
|
g.GET("", handler("stats", func(c *gin.Context) (interface{}, error) {
|
||||||
return getScope(c).Stats, nil
|
return getScope(c).Stats, nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ func Stats(g *gin.RouterGroup, db database.Database) {
|
|||||||
return stat, nil
|
return stat, nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
g.POST("/", handler("stat", func(c *gin.Context) (interface{}, error) {
|
g.POST("", handler("stat", func(c *gin.Context) (interface{}, error) {
|
||||||
stat := &models.Stat{}
|
stat := &models.Stat{}
|
||||||
err := c.BindJSON(stat)
|
err := c.BindJSON(stat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Vendored
+1
@@ -17,6 +17,7 @@ declare global {
|
|||||||
// interface Session {}
|
// interface Session {}
|
||||||
interface Stuff {
|
interface Stuff {
|
||||||
scope?: Scope
|
scope?: Scope
|
||||||
|
scopePath?: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import config from "$lib/config";
|
import config from "$lib/config";
|
||||||
|
import type { ScopeEntry } from "$lib/models/scope";
|
||||||
import type { Handle } from "@sveltejs/kit";
|
import type { Handle } from "@sveltejs/kit";
|
||||||
|
|
||||||
export const handle: Handle = async({ event, resolve }) => {
|
export const handle: Handle = async({ event, resolve }) => {
|
||||||
if (process.env.STUFFLOG3_USE_DUMMY_USER === "true") {
|
if (process.env.STUFFLOG3_USE_DUMMY_USER === "true") {
|
||||||
event.locals.user = {
|
event.locals.user = {
|
||||||
id: "c11230be-4912-4313-83b0-410a248b5bd1",
|
id: "c11230be-4912-4313-83b0-410a248b5bd1",
|
||||||
name: "Developer",
|
name: "DevMan",
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
event.locals.user = {id: "", name: "Guest"};
|
event.locals.user = {id: "", name: "Guest"};
|
||||||
@@ -14,8 +15,8 @@ export const handle: Handle = async({ event, resolve }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.locals.user.id !== "") {
|
if (event.locals.user.id !== "") {
|
||||||
const db = await config.database();
|
const res: {scopes: ScopeEntry[]} = await fetch(`${process.env.STUFFLOG3_API}/api/scopes`).then(r => r.json());
|
||||||
event.locals.scopes = await db.withUser(event.locals.user.id).scopes().list()
|
event.locals.scopes = res.scopes;
|
||||||
} else {
|
} else {
|
||||||
event.locals.scopes = [];
|
event.locals.scopes = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
export let scope: ScopeEntry
|
export let scope: ScopeEntry
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a href="/{scope.id}/">
|
<a href="/{scope.id}-{scope.abbreviation.toLocaleLowerCase()}/">
|
||||||
<CardEntry>
|
<CardEntry>
|
||||||
<EntryName subtitle={scope.abbreviation}>{scope.name}</EntryName>
|
<EntryName subtitle={scope.abbreviation}>{scope.name}</EntryName>
|
||||||
</CardEntry>
|
</CardEntry>
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
import type { Database } from "./database/interfaces";
|
const config = {};
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,12 +4,16 @@ import type { StatProgressEntry } from "./stat";
|
|||||||
|
|
||||||
export default interface Item {
|
export default interface Item {
|
||||||
id: number
|
id: number
|
||||||
|
scopeId: number
|
||||||
|
ownerId: string
|
||||||
|
projectId?: number
|
||||||
|
projectRequirementId?: number
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
createdTime: Date
|
createdTime: string
|
||||||
createdBy: string
|
acquireTime?: string
|
||||||
acquireTime?: Date
|
|
||||||
scheduledDate?: string
|
scheduledDate?: string
|
||||||
|
|
||||||
stats: StatProgressEntry[]
|
stats: StatProgressEntry[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
|
import type Item from "./item";
|
||||||
import type { ScopeEntry } from "./scope";
|
import type { ScopeEntry } from "./scope";
|
||||||
import type { StatProgressEntry } from "./stat";
|
import type { StatProgressEntry } from "./stat";
|
||||||
import type Status from "./status";
|
import type Status from "./status";
|
||||||
|
|
||||||
export default interface Project extends ProjectEntry {
|
export default interface Project extends ProjectEntry {
|
||||||
description: string
|
description: string
|
||||||
author: string
|
|
||||||
|
|
||||||
scope: ScopeEntry
|
|
||||||
requirements: Requirement[]
|
requirements: Requirement[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProjectEntry {
|
export interface ProjectEntry {
|
||||||
id: number
|
id: number
|
||||||
|
ownerId: string
|
||||||
|
createdTime: string
|
||||||
name: string
|
name: string
|
||||||
status: Status
|
status: Status
|
||||||
}
|
}
|
||||||
@@ -23,6 +23,7 @@ export interface Requirement {
|
|||||||
status: Status
|
status: Status
|
||||||
|
|
||||||
stats: StatProgressEntry[]
|
stats: StatProgressEntry[]
|
||||||
|
items: Item[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StandaloneRequirement extends Requirement {
|
export interface StandaloneRequirement extends Requirement {
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
import type { ProjectEntry } from "./project";
|
import type { ProjectEntry } from "./project";
|
||||||
import type Stat from "./stat";
|
import type Stat from "./stat";
|
||||||
|
import type Status from "./status";
|
||||||
export default interface Scope extends ScopeEntry {
|
|
||||||
projects: ProjectEntry[]
|
|
||||||
stats: Stat[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScopeEntry {
|
export interface ScopeEntry {
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
abbreviation: string
|
abbreviation: string
|
||||||
displayName: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScopeInput {
|
export default interface Scope extends ScopeEntry {
|
||||||
name: string
|
|
||||||
abbreviation: string
|
|
||||||
displayName: string
|
displayName: string
|
||||||
|
projects: ProjectEntry[]
|
||||||
|
stats: Stat[]
|
||||||
|
statusLabels: Record<Status, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScopeMember {
|
export interface ScopeMember {
|
||||||
|
|||||||
@@ -18,35 +18,3 @@ export interface StatAggregate {
|
|||||||
acquired: number
|
acquired: number
|
||||||
required: number
|
required: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const EXAMPLE2: Stat = {
|
|
||||||
id: 12,
|
|
||||||
name: "Assets",
|
|
||||||
description: "The amount of 'assets' produced in the end. This does not contribute towards the weight",
|
|
||||||
weight: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
const EXAMPLE3: Stat = {
|
|
||||||
id: 12,
|
|
||||||
name: "Hard Surface Practice",
|
|
||||||
description: "A value of the asset complexity, indicating how much general modeling practice is involved.",
|
|
||||||
weight: 0.5,
|
|
||||||
allowedAmounts: {
|
|
||||||
"Rote": 1,
|
|
||||||
"Novel": 2,
|
|
||||||
"Tutorial": 4,
|
|
||||||
"Difficult": 7,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const EXAMPLE: Stat = {
|
|
||||||
id: 12,
|
|
||||||
name: "Complexity Points",
|
|
||||||
description: "A value of the asset complexity, indicating how much general modeling practice is involved.",
|
|
||||||
weight: 1,
|
|
||||||
allowedAmounts: {
|
|
||||||
"Small": 1,
|
|
||||||
"Moderate": 3,
|
|
||||||
"Complex": 7,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,22 +2,21 @@
|
|||||||
import type { Load } from "@sveltejs/kit/types/internal";
|
import type { Load } from "@sveltejs/kit/types/internal";
|
||||||
|
|
||||||
export const load: Load = async({ fetch, params }) => {
|
export const load: Load = async({ fetch, params }) => {
|
||||||
const res = await fetch(`/${params.scope}.json`);
|
try {
|
||||||
|
const res: {scope: Scope} = await fetch(`/api/scopes/${params.scope.split("-")[0]}`).then(r => r.json());
|
||||||
|
|
||||||
if (res.ok) {
|
return {
|
||||||
const data = await res.json();
|
props: {
|
||||||
|
...res,
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
...data,
|
|
||||||
},
|
},
|
||||||
stuff: {
|
stuff: {
|
||||||
scope: data.scope,
|
scope: res.scope,
|
||||||
|
scopePath: params.scope,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
} catch(_) {}
|
||||||
|
|
||||||
return {fallthrough: true};
|
return {fallthrough: true} as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const STATUS_ORDER = [
|
const STATUS_ORDER = [
|
||||||
@@ -38,6 +37,8 @@
|
|||||||
import MenuCategory from "$lib/components/layout/MenuCategory.svelte";
|
import MenuCategory from "$lib/components/layout/MenuCategory.svelte";
|
||||||
import type { ProjectEntry } from "$lib/models/project";
|
import type { ProjectEntry } from "$lib/models/project";
|
||||||
import MenuItem from "$lib/components/layout/MenuItem.svelte";
|
import MenuItem from "$lib/components/layout/MenuItem.svelte";
|
||||||
|
import {page} from "$app/stores";
|
||||||
|
import sluggify from "$lib/utils/slugify";
|
||||||
|
|
||||||
export let scope: Scope;
|
export let scope: Scope;
|
||||||
|
|
||||||
@@ -56,14 +57,14 @@
|
|||||||
<div class="column side">
|
<div class="column side">
|
||||||
<Header subtitle={scope.name}>{scope.abbreviation}</Header>
|
<Header subtitle={scope.name}>{scope.abbreviation}</Header>
|
||||||
<MenuCategory title="Scope">
|
<MenuCategory title="Scope">
|
||||||
<MenuItem href="/{scope.id}">Overview</MenuItem>
|
<MenuItem href="/{$page. stuff.scopePath}">Overview</MenuItem>
|
||||||
<MenuItem href="/{scope.id}/history">History</MenuItem>
|
<MenuItem href="/{$page.stuff.scopePath}/history">History</MenuItem>
|
||||||
</MenuCategory>
|
</MenuCategory>
|
||||||
{#each STATUS_ORDER as status (status)}
|
{#each STATUS_ORDER as status (status)}
|
||||||
{#if projectsByStatus[status].length > 0}
|
{#if projectsByStatus[status].length > 0}
|
||||||
<MenuCategory status={status}>
|
<MenuCategory status={status}>
|
||||||
{#each projectsByStatus[status] as project (project.id)}
|
{#each projectsByStatus[status] as project (project.id)}
|
||||||
<MenuItem href="/{scope.id}/project/{project.id}" subtitle={
|
<MenuItem href="/{$page.stuff.scopePath}/project/{project.id}-{sluggify(project.name)}" subtitle={
|
||||||
project.name.includes(": ") ? project.name.split(": ")[0] + ": " : ""
|
project.name.includes(": ") ? project.name.split(": ")[0] + ": " : ""
|
||||||
}>{
|
}>{
|
||||||
project.name.includes(": ") ? project.name.split(": ").slice(1).join(": ") : project.name
|
project.name.includes(": ") ? project.name.split(": ").slice(1).join(": ") : project.name
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -9,174 +9,9 @@ import type { StandaloneSprint } from "$lib/models/sprint"
|
|||||||
export const get: RequestHandler = async({locals}) => {
|
export const get: RequestHandler = async({locals}) => {
|
||||||
const scopes: ScopeEntry[] = locals.scopes;
|
const scopes: ScopeEntry[] = locals.scopes;
|
||||||
|
|
||||||
const items: StandaloneItem[] = [
|
const items: StandaloneItem[] = []
|
||||||
{
|
const requirements: StandaloneRequirement[] = []
|
||||||
id: 1, scope: scopes[0],
|
const sprints: StandaloneSprint[] = []
|
||||||
name: "Table",
|
|
||||||
description: "A table for the Redrock rec-room.",
|
|
||||||
acquireDate: "2022-03-14T00:00:00Z",
|
|
||||||
createdDate: new Date(Date.now() - 3623511).toISOString(),
|
|
||||||
stats: [
|
|
||||||
{id: 1, name: "Asset", weight: 0.2, required: 1, acquired: 1},
|
|
||||||
{id: 2, name: "Complexity", weight: 1, required: 3, acquired: 5},
|
|
||||||
],
|
|
||||||
project: {
|
|
||||||
id: 443,
|
|
||||||
name: "3D Maps: Proving the concept",
|
|
||||||
status: Status.Active
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2, scope: scopes[0],
|
|
||||||
name: "Datapad Set",
|
|
||||||
description: "I need one, at least, but more is better.",
|
|
||||||
createdDate: new Date(Date.now() - 3623511).toISOString(),
|
|
||||||
stats: [
|
|
||||||
{id: 1, name: "Asset", weight: 0.2, required: 1, acquired: 0},
|
|
||||||
{id: 2, name: "Complexity", weight: 1, required: 3, acquired: 0},
|
|
||||||
{id: 3, name: "Hard Surface", weight: 0.333, required: 1, acquired: 0},
|
|
||||||
],
|
|
||||||
project: {
|
|
||||||
id: 443,
|
|
||||||
name: "3D Maps: Proving the concept",
|
|
||||||
status: Status.Active
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3, scope: scopes[1],
|
|
||||||
name: "Enila/Renala: Technicalities",
|
|
||||||
description: "Renala is answering Leah's concern and takes the shift at the Respite.",
|
|
||||||
createdDate: new Date(Date.now() - 3623511).toISOString(),
|
|
||||||
stats: [
|
|
||||||
{id: 1, name: "Story", weight: 3, required: 1, acquired: 0},
|
|
||||||
{id: 2, name: "Story Word", weight: 0.002, required: 500, acquired: 0},
|
|
||||||
],
|
|
||||||
project: {
|
|
||||||
id: 1,
|
|
||||||
name: "Background Stories",
|
|
||||||
status: Status.Active
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const requirements: StandaloneRequirement[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: "Basic Furniture",
|
|
||||||
description: "I need a lot of it.",
|
|
||||||
project: {
|
|
||||||
id: 443,
|
|
||||||
name: "3D Maps: Proving the concept",
|
|
||||||
status: Status.Active
|
|
||||||
},
|
|
||||||
status: Status.Background,
|
|
||||||
stats: [
|
|
||||||
{id: 1, name: "Asset", weight: 0.2, acquired: 8, required: 15},
|
|
||||||
{id: 2, name: "Complexity", weight: 1, acquired: 69, required: 30},
|
|
||||||
{id: 3, name: "Hard Surface", weight: 0.333, acquired: 20, required: 5},
|
|
||||||
],
|
|
||||||
scope: scopes[0],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: "Catching Up",
|
|
||||||
description: "Write a few stories to get back up to speed with less written characters.",
|
|
||||||
project: {
|
|
||||||
id: 1,
|
|
||||||
name: "Background Stories",
|
|
||||||
status: Status.Active
|
|
||||||
},
|
|
||||||
status: Status.Completed,
|
|
||||||
stats: [
|
|
||||||
{id: 1, name: "Story", weight: 3, required: 10, acquired: 3},
|
|
||||||
{id: 2, name: "Story Word", weight: 0.002, required: 5000, acquired: 2173},
|
|
||||||
{id: 3, name: "Worldbuilding", weight: 0.5, required: 5, acquired: 1},
|
|
||||||
],
|
|
||||||
scope: scopes[1],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: "Chapter 3",
|
|
||||||
description: "it's go time",
|
|
||||||
project: {
|
|
||||||
id: 1,
|
|
||||||
name: "Wenera's Job",
|
|
||||||
status: Status.Active
|
|
||||||
},
|
|
||||||
status: Status.Available,
|
|
||||||
stats: [
|
|
||||||
{id: 1, name: "Story", weight: 3, required: 5, acquired: 1},
|
|
||||||
{id: 2, name: "Story Word", weight: 0.002, required: 4000, acquired: 759},
|
|
||||||
],
|
|
||||||
scope: scopes[1],
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const sprints: StandaloneSprint[] = [
|
|
||||||
{
|
|
||||||
id: 553,
|
|
||||||
name: "March Sprint",
|
|
||||||
description: "Hey kid, wanna model some trees?",
|
|
||||||
from: "2022-03-01T00:00:00+0200",
|
|
||||||
to: "2022-03-30T23:59:59+0200",
|
|
||||||
coarse: false,
|
|
||||||
kind: "stats",
|
|
||||||
scope: scopes[0],
|
|
||||||
timed: true,
|
|
||||||
stats: [
|
|
||||||
{id: 1, name: "Asset", weight: 0, acquired: 13, required: 15},
|
|
||||||
{id: 2, name: "Complexity", weight: 1, acquired: 37, required: 30},
|
|
||||||
{id: 3, name: "Hard Surface", weight: 0.25, acquired: 8, required: 5},
|
|
||||||
{id: 4, name: "UV Mapping", weight: 0.25, acquired: 3, required: 5},
|
|
||||||
],
|
|
||||||
items: [],
|
|
||||||
requirements: [],
|
|
||||||
aggregateRequired: 30,
|
|
||||||
aggregateName: "3D Modeling",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 643,
|
|
||||||
name: "March Sprint",
|
|
||||||
description: "Crank out stories",
|
|
||||||
from: "2022-03-01T00:00:00+0200",
|
|
||||||
to: "2022-03-30T23:59:59+0200",
|
|
||||||
coarse: false,
|
|
||||||
kind: "requirements",
|
|
||||||
scope: scopes[1],
|
|
||||||
timed: true,
|
|
||||||
stats: [],
|
|
||||||
items: [],
|
|
||||||
requirements: [
|
|
||||||
requirements[1],
|
|
||||||
requirements[2],
|
|
||||||
],
|
|
||||||
aggregateRequired: 0,
|
|
||||||
aggregateName: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 771,
|
|
||||||
name: "March Sprint",
|
|
||||||
description: "Do these",
|
|
||||||
from: "2022-03-01T00:00:00+0200",
|
|
||||||
to: "2022-03-30T23:59:59+0200",
|
|
||||||
coarse: false,
|
|
||||||
kind: "items",
|
|
||||||
scope: scopes[0],
|
|
||||||
timed: true,
|
|
||||||
stats: [
|
|
||||||
{id: 1, name: "Asset", weight: 0.2, required: 2, acquired: 1},
|
|
||||||
{id: 2, name: "Complexity", weight: 1, required: 8, acquired: 5},
|
|
||||||
{id: 3, name: "Hard Surface", weight: 0.333, required: 1, acquired: 0},
|
|
||||||
],
|
|
||||||
items: [
|
|
||||||
items[0],
|
|
||||||
items[1],
|
|
||||||
],
|
|
||||||
requirements: [],
|
|
||||||
aggregateRequired: 0,
|
|
||||||
aggregateName: "",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ func (r *scopeRepository) Find(ctx context.Context, id int, full bool) (*models.
|
|||||||
Name: scope.Name,
|
Name: scope.Name,
|
||||||
Abbreviation: scope.Abbreviation,
|
Abbreviation: scope.Abbreviation,
|
||||||
},
|
},
|
||||||
|
Members: []models.ScopeMember{},
|
||||||
|
Projects: []models.ProjectEntry{},
|
||||||
|
Stats: []models.Stat{},
|
||||||
|
StatusLabels: make(map[models.Status]string),
|
||||||
}
|
}
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import "time"
|
|||||||
type Item struct {
|
type Item struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
ScopeID int `json:"scopeId"`
|
ScopeID int `json:"scopeId"`
|
||||||
OwnerID string `json:"-"`
|
OwnerID string `json:"ownerId"`
|
||||||
ProjectID *int `json:"projectId,omitempty"`
|
ProjectID *int `json:"projectId,omitempty"`
|
||||||
ProjectRequirementID *int `json:"projectRequirementId,omitempty"`
|
ProjectRequirementID *int `json:"projectRequirementId,omitempty"`
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ type Scope struct {
|
|||||||
ScopeEntry
|
ScopeEntry
|
||||||
|
|
||||||
DisplayName string `json:"displayName"`
|
DisplayName string `json:"displayName"`
|
||||||
Members []ScopeMember `json:"members,omitempty"`
|
Members []ScopeMember `json:"members"`
|
||||||
Projects []ProjectEntry `json:"projects,omitempty"`
|
Projects []ProjectEntry `json:"projects"`
|
||||||
Stats []Stat `json:"stats,omitempty"`
|
Stats []Stat `json:"stats"`
|
||||||
StatusLabels map[Status]string `json:"statusLabels,omitempty"` // Not stored as part of scope, but may be in the future.
|
StatusLabels map[Status]string `json:"statusLabels"` // Not stored as part of scope, but may be in the future.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scope) Member(id string) *ScopeMember {
|
func (s *Scope) Member(id string) *ScopeMember {
|
||||||
|
|||||||
Reference in New Issue
Block a user