diff --git a/backend/src/graphql/resolvers/index.ts b/backend/src/graphql/resolvers/index.ts index 5bdee49..7dfbfdc 100644 --- a/backend/src/graphql/resolvers/index.ts +++ b/backend/src/graphql/resolvers/index.ts @@ -15,6 +15,7 @@ import { threatActorResolvers } from "./threatActorResolver.js"; import { techniqueResolvers } from "./techniqueResolver.js"; import { softwareResolvers } from "./softwareResolver.js"; +import { tacticResolvers } from "./tacticResolver.js"; export const resolvers = { @@ -22,5 +23,6 @@ export const resolvers = { ...threatActorResolvers.Query, ...techniqueResolvers.Query, ...softwareResolvers.Query, + ...tacticResolvers.Query, }, }; \ No newline at end of file diff --git a/backend/src/graphql/resolvers/tacticResolver.ts b/backend/src/graphql/resolvers/tacticResolver.ts index c0d3458..4c8e263 100644 --- a/backend/src/graphql/resolvers/tacticResolver.ts +++ b/backend/src/graphql/resolvers/tacticResolver.ts @@ -9,4 +9,25 @@ * * * GraphQL -> Resolver -> Repository -> Drizzle -> MySQL - */ \ No newline at end of file + */ + +import { + getAllTactics, + getTacticByMitreId, + getTacticTechniques, +} from "../../repositories/tacticRepository.js"; + +export const tacticResolvers = { + Query: { + allTactics: async () => getAllTactics(), + + getTacticByMitreId: async (_parent: unknown, args: { mitreId: string }) => { + return getTacticByMitreId(args.mitreId); + }, + + getTacticTechniques: async ( + _parent: unknown, + args: { mitreId: string; limit: number; offset: number }, + ) => getTacticTechniques(args.mitreId, args.limit, args.offset), + }, +}; \ No newline at end of file diff --git a/backend/src/graphql/typeDefs.ts b/backend/src/graphql/typeDefs.ts index 000a6f8..027d9d1 100644 --- a/backend/src/graphql/typeDefs.ts +++ b/backend/src/graphql/typeDefs.ts @@ -50,6 +50,27 @@ export const typeDefs = `#graphql description: String! } + type TacticGrid { + mitreId: String! + name: String! + description: String! + techniqueCount: Int! + } + + type TacticDetailed { + tacticId: String! + mitreId: String! + name: String! + description: String! + tacticEntryCreated: String! + xMitreDeprecated: Boolean! + } + + type TechniquePage { + items: [Technique!]! + totalCount: Int! + } + type RelatedSoftwareEntity { id: String! mitreId: String! @@ -73,9 +94,15 @@ export const typeDefs = `#graphql getThreatActorByMitreId(mitreId: String!): ThreatActorDetailed getThreatActorTechniques(mitreId: String!, limit: Int!, offset: Int!): AssociatedItemsPage! getThreatActorSoftwares(mitreId: String!, limit: Int!, offset: Int!): AssociatedItemsPage! + allTechniques: [Technique!]! techniqueByMitreId(mitreId: String!): Technique + software: [Software!]! softwareByMitreId(mitreId: String!): Software + + allTactics: [TacticGrid!]! + getTacticByMitreId(mitreId: String!): TacticDetailed + getTacticTechniques(mitreId: String!, limit: Int!, offset: Int!): TechniquePage! } `; diff --git a/backend/src/repositories/tacticRepository.ts b/backend/src/repositories/tacticRepository.ts index 3031feb..2a69fb0 100644 --- a/backend/src/repositories/tacticRepository.ts +++ b/backend/src/repositories/tacticRepository.ts @@ -13,4 +13,66 @@ * * * !!!ALTSÃ… HER SKRIVER VI KODEN SOM FAKTISK HENTER UT DATA FRA DB VIA DRIZZLE!!!! - */ \ No newline at end of file + */ + +import { count, eq, sql } from "drizzle-orm"; + +import { db } from "../db/connection.js"; +import { tactic, tacticTechnique, technique } from "../db/schema.js"; + +export async function getAllTactics() { + return db + .select({ + mitreId: tactic.mitre_id, + name: tactic.name, + description: tactic.description, + techniqueCount: count(tacticTechnique.technique_id), + }) + .from(tactic) + .leftJoin(tacticTechnique, eq(tactic.tactic_id, tacticTechnique.tactic_id)) + .groupBy(tactic.tactic_id) + .orderBy(tactic.mitre_id); +} + +export async function getTacticByMitreId(mitreId: string) { + const [result] = await db + .select({ + tacticId: tactic.tactic_id, + mitreId: tactic.mitre_id, + name: tactic.name, + description: tactic.description, + tacticEntryCreated: sql`DATE_FORMAT(${tactic.tactic_entry_created}, '%Y-%m-%d')`, + xMitreDeprecated: tactic.x_mitre_deprecated, + }) + .from(tactic) + .where(eq(tactic.mitre_id, mitreId)) + .limit(1); + + return result ?? null; +} + +export async function getTacticTechniques(mitreId: string, limit: number, offset: number) { + const [techniques, countResult] = await Promise.all([ + db.select({ + mitreId: technique.mitre_id, + name: technique.name, + description: technique.description, + }) + .from(tacticTechnique) + .innerJoin(tactic, eq(tacticTechnique.tactic_id, tactic.tactic_id)) + .innerJoin(technique, eq(tacticTechnique.technique_id, technique.technique_id)) + .where(eq(tactic.mitre_id, mitreId)) + .orderBy(technique.mitre_id) + .limit(limit) + .offset(offset), + db.select({ totalCount: count() }) + .from(tacticTechnique) + .innerJoin(tactic, eq(tacticTechnique.tactic_id, tactic.tactic_id)) + .where(eq(tactic.mitre_id, mitreId)), + ]); + + return { + items: techniques, + totalCount: countResult[0]?.totalCount ?? 0, + }; +} \ No newline at end of file