progress on reputation service
This commit is contained in:
parent
da892c0221
commit
667ed43792
|
@ -0,0 +1,32 @@
|
||||||
|
import { randomUUID } from "crypto";
|
||||||
|
import { Vertex } from "./vertex";
|
||||||
|
|
||||||
|
export class Edge {
|
||||||
|
|
||||||
|
private id: string;
|
||||||
|
private category: string;
|
||||||
|
private directional: boolean;
|
||||||
|
private parentVertex: Vertex;
|
||||||
|
private childVertex: Vertex;
|
||||||
|
|
||||||
|
constructor(id: null | string, parent: Vertex, child: Vertex, category = "generic", directional = false) {
|
||||||
|
this.id ? id : randomUUID();
|
||||||
|
this.parentVertex = parent;
|
||||||
|
this.childVertex = child;
|
||||||
|
this.category = category;
|
||||||
|
this.directional = directional;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getId() { return this.id; }
|
||||||
|
public getCategory() { return this.category; }
|
||||||
|
public getDirectional() { return this.directional; }
|
||||||
|
public getParentVertex() { return this.parentVertex; }
|
||||||
|
public getChildVertex() { return this.childVertex; }
|
||||||
|
|
||||||
|
public getAdjacent(vertex: Vertex) {
|
||||||
|
if (vertex == this.childVertex) return this.parentVertex;
|
||||||
|
else if (vertex == this.parentVertex) return this.childVertex;
|
||||||
|
else throw new Error(`Edge ${this.id} is not connected to vertex ${vertex.getId()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { Edge } from "./edge";
|
||||||
|
import { Vertex } from "./vertex";
|
||||||
|
|
||||||
|
export class Graph {
|
||||||
|
|
||||||
|
private vertices: Map<string, Vertex>;
|
||||||
|
private edges: Map<string, Edge>;
|
||||||
|
|
||||||
|
constructor(vertices: Vertex[] = [], edges: Edge[] = []) {
|
||||||
|
this.vertices = new Map();
|
||||||
|
this.edges = new Map();
|
||||||
|
vertices.forEach(this.addVertex);
|
||||||
|
edges.forEach(this.addEdge);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAllVertices() {
|
||||||
|
return Array.from(this.vertices.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public getVertex(id: string) {
|
||||||
|
return this.vertices.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addVertex(vertex: Vertex) {
|
||||||
|
this.vertices.set(vertex.getId(), vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteVertex(vertex: string | Vertex) {
|
||||||
|
if (vertex instanceof Vertex) vertex = vertex.getId();
|
||||||
|
this.vertices.delete(vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAllEdges() {
|
||||||
|
return Array.from(this.edges.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public getEdge(id: string) {
|
||||||
|
return this.edges.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addEdge(edge: Edge) {
|
||||||
|
edge.getParentVertex().addEdge(edge);
|
||||||
|
edge.getChildVertex().addEdge(edge);
|
||||||
|
this.edges.set(edge.getId(), edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteEdge(edge: string | Edge) {
|
||||||
|
if (typeof edge == "string") edge = this.edges.get(edge);
|
||||||
|
if (!edge) throw new Error(`Failed to identify edge with an ID of ${edge.getId()}.`);
|
||||||
|
edge.getParentVertex().removeEdge(edge);
|
||||||
|
edge.getChildVertex().removeEdge(edge);
|
||||||
|
this.edges.delete(edge.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { randomUUID } from "crypto";
|
||||||
|
import { Edge } from "./edge";
|
||||||
|
|
||||||
|
export class Vertex {
|
||||||
|
|
||||||
|
private id: string
|
||||||
|
private degree: number;
|
||||||
|
private edges: Edge[];
|
||||||
|
private data: any;
|
||||||
|
|
||||||
|
constructor(id: null | string, data: any, edges: Edge[] = []) {
|
||||||
|
this.id = id ? id : randomUUID();
|
||||||
|
this.data = data;
|
||||||
|
this.degree = edges.length;
|
||||||
|
this.edges = edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getId() { return this.id; }
|
||||||
|
public getDegree() { return this.degree; }
|
||||||
|
public getEdges() { return this.edges; }
|
||||||
|
public getData() { return this.data; }
|
||||||
|
|
||||||
|
public updateData(data: any) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addEdge(edge: Edge) {
|
||||||
|
this.edges.push(edge);
|
||||||
|
this.degree++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeEdge(edge: Edge) {
|
||||||
|
const index = this.edges.indexOf(edge);
|
||||||
|
if (index == -1) throw new Error(`Unable to identify edge ${edge.getId()} from vertex ${this.id}.`);
|
||||||
|
this.edges.splice(index, 1);
|
||||||
|
this.degree--;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
export class ReputationService {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Post } from "./post";
|
||||||
|
|
||||||
|
export class Citation {
|
||||||
|
|
||||||
|
private sourcePost: Post;
|
||||||
|
private citedPost: Post;
|
||||||
|
private impact: number
|
||||||
|
|
||||||
|
constructor(source: Post, cited:Post, impact: number) {
|
||||||
|
this.sourcePost = source;
|
||||||
|
this.citedPost = cited;
|
||||||
|
this.impact = impact;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSourcePost() { return this.sourcePost; }
|
||||||
|
public getCitedPost() { return this.citedPost; }
|
||||||
|
public getImpact() { return this.impact; }
|
||||||
|
|
||||||
|
public isNeutral() { return this.impact == 0; }
|
||||||
|
public isNegative() { return this.impact < 0; }
|
||||||
|
public isPositive() { return this.impact > 0; }
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { randomUUID } from "crypto";
|
||||||
|
|
||||||
|
export interface LedgerEntry {
|
||||||
|
timestamp: number,
|
||||||
|
type: ('post' | 'citation'),
|
||||||
|
postId: string,
|
||||||
|
citationId: string | null,
|
||||||
|
change: number,
|
||||||
|
balance: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Member {
|
||||||
|
|
||||||
|
static localStore: Map<string, Member>;
|
||||||
|
|
||||||
|
private id: string;
|
||||||
|
private reputation: number;
|
||||||
|
private ledger: LedgerEntry[];
|
||||||
|
|
||||||
|
constructor(id?: string) {
|
||||||
|
this.id = id ? id : randomUUID();
|
||||||
|
this.reputation = 0;
|
||||||
|
this.ledger = [];
|
||||||
|
Member.localStore.set(this.id, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getAllMembers() {
|
||||||
|
return Array.from(Member.localStore.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getMember(id: string) {
|
||||||
|
return Member.localStore.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getId() { return this.id; }
|
||||||
|
public getReputation() { return this.reputation; }
|
||||||
|
public getLedger() { return this.ledger; }
|
||||||
|
|
||||||
|
public postReputation(postId: string, amount: number) {
|
||||||
|
const entry: LedgerEntry = {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
type: "post",
|
||||||
|
postId: postId,
|
||||||
|
citationId: null,
|
||||||
|
change: amount,
|
||||||
|
balance: this.reputation + amount
|
||||||
|
}
|
||||||
|
this.reputation = entry.balance;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public citationReputation(postId: string, citationId: string, amount: number) {
|
||||||
|
const entry: LedgerEntry = {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
type: "citation",
|
||||||
|
postId: postId,
|
||||||
|
citationId: citationId,
|
||||||
|
change: amount,
|
||||||
|
balance: this.reputation + amount
|
||||||
|
}
|
||||||
|
this.reputation = entry.balance;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { Citation } from "./citation";
|
||||||
|
import { Member } from "./member";
|
||||||
|
|
||||||
|
export interface AuthorWeight {
|
||||||
|
weight: number;
|
||||||
|
author: Member
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CitationWeight {
|
||||||
|
weight: number;
|
||||||
|
citation: Citation;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Post {
|
||||||
|
|
||||||
|
private static DEFAULT_WEIGHT = 1000;
|
||||||
|
|
||||||
|
private title: string;
|
||||||
|
private content: string;
|
||||||
|
private authors: AuthorWeight[];
|
||||||
|
private citations: CitationWeight[];
|
||||||
|
private authorsWeightTotal: number;
|
||||||
|
private citationsWeightTotal: number;
|
||||||
|
|
||||||
|
constructor (title: string, content: string, authors: Member | Member[]) {
|
||||||
|
this.title = title;
|
||||||
|
this.content = content;
|
||||||
|
this.authors = [];
|
||||||
|
this.citations = [];
|
||||||
|
this.authorsWeightTotal = 0;
|
||||||
|
this.citationsWeightTotal = 0;
|
||||||
|
if (!Array.isArray(authors)) authors = [authors];
|
||||||
|
for (const author of <Member[]> authors) {
|
||||||
|
this.authorsWeightTotal += Post.DEFAULT_WEIGHT;
|
||||||
|
this.authors.push({
|
||||||
|
weight: Post.DEFAULT_WEIGHT,
|
||||||
|
author: author
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static serialize(post: Post, pretty: boolean = true) {
|
||||||
|
//TODO: Need to convert citations and authors to IDs.
|
||||||
|
if (!pretty) return JSON.stringify(Post);
|
||||||
|
return JSON.stringify(Post, undefined, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static parse(serializedPost: string) {
|
||||||
|
//TODO: Need to retrieve citations and authors from IDs.
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(serializedPost);
|
||||||
|
const title = <string> parsed?.title;
|
||||||
|
const content = <string> parsed.content;
|
||||||
|
const authors = <AuthorWeight[]> parsed.authors;
|
||||||
|
const citations = <CitationWeight[]> parsed.citations;
|
||||||
|
const post = new Post(title, content, authors[0].author);
|
||||||
|
for (const author of authors) post.setAuthor(author.author, author.weight);
|
||||||
|
for (const citation of citations) post.setCitation(citation.citation, citation.weight);
|
||||||
|
return post;
|
||||||
|
} catch(err) {
|
||||||
|
throw new Error("Failed to parse the serialized post. \n - " + err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTitle() { return this.title; }
|
||||||
|
public getContent() { return this.content; }
|
||||||
|
public getAuthors() { return this.authors.map(({author}) => author); }
|
||||||
|
public getCitations() { return this.citations.map(({citation}) => citation); }
|
||||||
|
public getAuthorsWeightTotal() { return this.authorsWeightTotal; }
|
||||||
|
public getCitationWeightTotal() { return this.citationsWeightTotal; }
|
||||||
|
|
||||||
|
public setAuthor(author: Member, weight: number = Post.DEFAULT_WEIGHT) {
|
||||||
|
this.removeAuthor(author);
|
||||||
|
this.authors.push({author, weight});
|
||||||
|
this.authorsWeightTotal += weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeAuthor(author: Member) {
|
||||||
|
const index = this.getAuthors().indexOf(author);
|
||||||
|
if (index != -1) {
|
||||||
|
const current = this.authors[index];
|
||||||
|
this.authorsWeightTotal -= current.weight;
|
||||||
|
this.authors.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public setCitation(citation: Citation, weight: number = Post.DEFAULT_WEIGHT) {
|
||||||
|
this.removeCitation(citation);
|
||||||
|
this.citations.push({citation, weight});
|
||||||
|
this.citationsWeightTotal += weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public removeCitation(citation: Citation) {
|
||||||
|
const index = this.getCitations().indexOf(citation);
|
||||||
|
if (index != -1) {
|
||||||
|
const current = this.citations[index];
|
||||||
|
this.citationsWeightTotal -= current.weight;
|
||||||
|
this.citations.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWeight(weighted: Member | Citation) {
|
||||||
|
const forAuthor = weighted instanceof Member;
|
||||||
|
const arr = forAuthor ? this.authors : this.citations;
|
||||||
|
const key = forAuthor ? "author" : "citation";
|
||||||
|
const index = arr.findIndex(item => item[key] == weighted);
|
||||||
|
if (index == -1) return -1;
|
||||||
|
return arr[index].weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue