Skip to content

Commit

Permalink
Merge pull request #21 from secure-dashboards/feat/add-github-orgs
Browse files Browse the repository at this point in the history
  • Loading branch information
UlisesGascon authored Dec 4, 2024
2 parents a97b4e2 + 6d2ad24 commit 96dd002
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 18 deletions.
20 changes: 15 additions & 5 deletions __tests__/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const inquirer = require('inquirer').default
const knexInit = require('knex')
const { getConfig } = require('../src/config/')
const { runAddProjectCommand } = require('../src/cli')
const { resetDatabase, getAllProjects } = require('./utils')
const { resetDatabase, getAllProjects, getAllGithubOrgs } = require('./utils')

const { dbSettings } = getConfig('test')

Expand Down Expand Up @@ -35,12 +35,17 @@ describe('Interactive Mode', () => {
test('Add a project with name, GitHub URLs, and category', async () => {
let projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
let githubOrgs = await getAllGithubOrgs(knex)
expect(githubOrgs.length).toBe(0)
await runAddProjectCommand(knex, {})
projects = await getAllProjects(knex)
expect(projects.length).toBe(1)
expect(projects[0].name).toBe('eslint')
expect(projects[0].category).toBe('impact')
// @TODO: Add test for githubUrls when it is implemented
githubOrgs = await getAllGithubOrgs(knex)
expect(githubOrgs.length).toBe(1)
expect(githubOrgs[0].login).toBe('eslint')
expect(githubOrgs[0].html_url).toBe('https://github.com/eslint')
})

test('Prevent to add a project that already exists', async () => {
Expand All @@ -51,7 +56,7 @@ describe('Interactive Mode', () => {
expect(projects.length).toBe(1)
await expect(runAddProjectCommand(knex, {}))
.rejects
.toThrow('Project with name eslint already exists')
.toThrow('Project with name (eslint) already exists')
projects = await getAllProjects(knex)
expect(projects.length).toBe(1)
})
Expand All @@ -61,12 +66,17 @@ describe('Non-Interactive Mode', () => {
test('Add a project with name, GitHub URLs, and category', async () => {
let projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
let githubOrgs = await getAllGithubOrgs(knex)
expect(githubOrgs.length).toBe(0)
await runAddProjectCommand(knex, { name: 'eslint', githubUrls: ['https://github.com/eslint'], category: 'impact' })
projects = await getAllProjects(knex)
expect(projects.length).toBe(1)
expect(projects[0].name).toBe('eslint')
expect(projects[0].category).toBe('impact')
// @TODO: Add test for githubUrls when it is implemented
githubOrgs = await getAllGithubOrgs(knex)
expect(githubOrgs.length).toBe(1)
expect(githubOrgs[0].login).toBe('eslint')
expect(githubOrgs[0].html_url).toBe('https://github.com/eslint')
})

test('Prevent to add a project that already exists', async () => {
Expand All @@ -77,7 +87,7 @@ describe('Non-Interactive Mode', () => {
expect(projects.length).toBe(1)
await expect(runAddProjectCommand(knex, { name: 'eslint', githubUrls: ['https://github.com/eslint'], category: 'impact' }))
.rejects
.toThrow('Project with name eslint already exists')
.toThrow('Project with name (eslint) already exists')
projects = await getAllProjects(knex)
expect(projects.length).toBe(1)
})
Expand Down
10 changes: 8 additions & 2 deletions __tests__/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
const resetDatabase = (knex) => knex('projects').del()
const resetDatabase = async (knex) => {
await knex('github_organizations').del()
await knex('projects').del()
}

const getAllProjects = (knex) => knex('projects').select('*')
const getAllGithubOrgs = (knex) => knex('github_organizations').select('*')

module.exports = {
resetDatabase,
getAllProjects
getAllProjects,
getAllGithubOrgs
}
13 changes: 6 additions & 7 deletions src/cli/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const { initializeStore } = require('../store')
const { projectCategories } = getConfig()

async function runAddProjectCommand (knex, options = {}) {
const { addProject } = initializeStore(knex)
const { addProject, addGithubOrganization } = initializeStore(knex)

if (Object.keys(options).length > 0) {
if (!options.name) {
Expand Down Expand Up @@ -90,16 +90,15 @@ async function runAddProjectCommand (knex, options = {}) {

await addProject({
name: answers.name.toLowerCase(),
category: answers.category,
githubOrgs: answers.githubUrls.map((url) => ({
url,
name: url.split('https://github.com/')[1]
}))
category: answers.category
})

debug(`Project (${answers.name}) added successfully!`)

// @TODO: Add Organizations to the database
await Promise.all(answers.githubUrls.map((url) => addGithubOrganization({
html_url: url,
login: url.split('https://github.com/')[1]
})))

return answers
}
Expand Down
Empty file.
83 changes: 83 additions & 0 deletions src/database/migrations/1733318617773_add_github_orgs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
exports.up = async (knex) => {
// Create 'github_organizations' table
await knex.schema.createTable('github_organizations', (table) => {
table.increments('id').primary() // Primary key
table.string('login').unique().notNullable()
table.integer('github_org_id').unique()
table.string('node_id')
table.string('url')
table.string('repos_url')
table.string('avatar_url')
table.text('description')
table.string('name')
table.string('company')
table.string('blog')
table.string('location')
table.string('email')
table.string('twitter_username')
table.boolean('is_verified')
table.boolean('has_organization_projects')
table.boolean('has_repository_projects')
table.integer('public_repos')
table.integer('public_gists')
table.integer('followers')
table.integer('following')
table.string('html_url').notNullable()
table.integer('total_private_repos')
table.integer('owned_private_repos')
table.integer('private_gists')
table.integer('disk_usage')
table.integer('collaborators')
table.string('default_repository_permission')
table.boolean('members_can_create_repositories')
table.boolean('two_factor_requirement_enabled')
table.string('members_allowed_repository_creation_type')
table.boolean('members_can_create_public_repositories')
table.boolean('members_can_create_private_repositories')
table.boolean('members_can_create_internal_repositories')
table.boolean('members_can_create_pages')
table.boolean('members_can_create_public_pages')
table.boolean('members_can_create_private_pages')
table.boolean('members_can_fork_private_repositories')
table.boolean('web_commit_signoff_required')
table.boolean('deploy_keys_enabled_for_repositories')
table.boolean('dependency_graph_enabled_for_new_repositories')
table.boolean('dependabot_alerts_enabled_for_new_repositories')
table.boolean('dependabot_security_updates_enabled_for_new_repositories')
table.boolean('advanced_security_enabled_for_new_repositories')
table.boolean('secret_scanning_enabled_for_new_repositories')
table.boolean('secret_scanning_push_protection_enabled_for_new_repositories')
table.string('secret_scanning_push_protection_custom_link')
table.boolean('secret_scanning_push_protection_custom_link_enabled')
table.timestamp('github_created_at')
table.timestamp('github_updated_at')
table.timestamp('github_archived_at')
table.timestamp('created_at').defaultTo(knex.fn.now()).notNullable()
table.timestamp('updated_at').defaultTo(knex.fn.now()).notNullable()

// Foreign key to 'projects' table
table.integer('project_id')
.unsigned()
.references('id')
.inTable('projects')
.onDelete('CASCADE') // Deletes organization if the project is deleted
.onUpdate('CASCADE') // Updates organization if the project ID is updated
})

// Add trigger to 'github_organizations' table
await knex.raw(`
CREATE TRIGGER set_updated_at_github_organizations
BEFORE UPDATE ON github_organizations
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
`)
}

exports.down = async (knex) => {
// Drop triggers
await knex.raw('DROP TRIGGER IF EXISTS set_updated_at_github_organizations ON github_organizations;')
// Drop table
await knex.schema.dropTableIfExists('github_organizations')
// Drop the reusable function
await knex.raw('DROP FUNCTION IF EXISTS update_updated_at_column;')
}
128 changes: 128 additions & 0 deletions src/database/schema/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,88 @@ SET default_tablespace = '';

SET default_table_access_method = heap;

--
-- Name: github_organizations; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.github_organizations (
id integer NOT NULL,
login character varying(255) NOT NULL,
github_org_id integer,
node_id character varying(255),
url character varying(255),
repos_url character varying(255),
avatar_url character varying(255),
description text,
name character varying(255),
company character varying(255),
blog character varying(255),
location character varying(255),
email character varying(255),
twitter_username character varying(255),
is_verified boolean,
has_organization_projects boolean,
has_repository_projects boolean,
public_repos integer,
public_gists integer,
followers integer,
following integer,
html_url character varying(255) NOT NULL,
total_private_repos integer,
owned_private_repos integer,
private_gists integer,
disk_usage integer,
collaborators integer,
default_repository_permission character varying(255),
members_can_create_repositories boolean,
two_factor_requirement_enabled boolean,
members_allowed_repository_creation_type character varying(255),
members_can_create_public_repositories boolean,
members_can_create_private_repositories boolean,
members_can_create_internal_repositories boolean,
members_can_create_pages boolean,
members_can_create_public_pages boolean,
members_can_create_private_pages boolean,
members_can_fork_private_repositories boolean,
web_commit_signoff_required boolean,
deploy_keys_enabled_for_repositories boolean,
dependency_graph_enabled_for_new_repositories boolean,
dependabot_alerts_enabled_for_new_repositories boolean,
dependabot_security_updates_enabled_for_new_repositories boolean,
advanced_security_enabled_for_new_repositories boolean,
secret_scanning_enabled_for_new_repositories boolean,
secret_scanning_push_protection_enabled_for_new_repositories boolean,
secret_scanning_push_protection_custom_link character varying(255),
secret_scanning_push_protection_custom_link_enabled boolean,
github_created_at timestamp with time zone,
github_updated_at timestamp with time zone,
github_archived_at timestamp with time zone,
created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
project_id integer
);


--
-- Name: github_organizations_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.github_organizations_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;


--
-- Name: github_organizations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.github_organizations_id_seq OWNED BY public.github_organizations.id;


--
-- Name: knex_migrations; Type: TABLE; Schema: public; Owner: -
--
Expand Down Expand Up @@ -131,6 +213,13 @@ CREATE SEQUENCE public.projects_id_seq
ALTER SEQUENCE public.projects_id_seq OWNED BY public.projects.id;


--
-- Name: github_organizations id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.github_organizations ALTER COLUMN id SET DEFAULT nextval('public.github_organizations_id_seq'::regclass);


--
-- Name: knex_migrations id; Type: DEFAULT; Schema: public; Owner: -
--
Expand All @@ -152,6 +241,30 @@ ALTER TABLE ONLY public.knex_migrations_lock ALTER COLUMN index SET DEFAULT next
ALTER TABLE ONLY public.projects ALTER COLUMN id SET DEFAULT nextval('public.projects_id_seq'::regclass);


--
-- Name: github_organizations github_organizations_github_org_id_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.github_organizations
ADD CONSTRAINT github_organizations_github_org_id_unique UNIQUE (github_org_id);


--
-- Name: github_organizations github_organizations_login_unique; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.github_organizations
ADD CONSTRAINT github_organizations_login_unique UNIQUE (login);


--
-- Name: github_organizations github_organizations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.github_organizations
ADD CONSTRAINT github_organizations_pkey PRIMARY KEY (id);


--
-- Name: knex_migrations_lock knex_migrations_lock_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
Expand All @@ -176,13 +289,28 @@ ALTER TABLE ONLY public.projects
ADD CONSTRAINT projects_pkey PRIMARY KEY (id);


--
-- Name: github_organizations set_updated_at_github_organizations; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER set_updated_at_github_organizations BEFORE UPDATE ON public.github_organizations FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();


--
-- Name: projects set_updated_at_projects; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER set_updated_at_projects BEFORE UPDATE ON public.projects FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();


--
-- Name: github_organizations github_organizations_project_id_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.github_organizations
ADD CONSTRAINT github_organizations_project_id_foreign FOREIGN KEY (project_id) REFERENCES public.projects(id) ON UPDATE CASCADE ON DELETE CASCADE;


--
-- PostgreSQL database dump complete
--
Expand Down
22 changes: 18 additions & 4 deletions src/store/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
const debug = require('debug')('store')

const addGithubOrganization = knex => async (organization) => {
const organizationExists = await knex('github_organizations').where({ html_url: organization.html_url }).first()
debug(`Checking if organization (${organization.login}) exists...`)
if (organizationExists) {
throw new Error(`Organization with login (${organization.login}) already exists`)
}
debug(`Inserting organization (${organization.login})...`)
return knex('github_organizations').insert({
html_url: organization.html_url,
login: organization.login
})
}

const addProject = knex => async (project) => {
const { name, category } = project
const projectExists = await knex('projects').where({ name }).first()
debug(`Checking if project ${name} exists...`)
debug(`Checking if project (${name}) exists...`)
if (projectExists) {
throw new Error(`Project with name ${name} already exists`)
throw new Error(`Project with name (${name}) already exists`)
}
debug(`Inserting project ${name}...`)
debug(`Inserting project (${name})...`)
return knex('projects').insert({
name,
category
Expand All @@ -17,7 +30,8 @@ const addProject = knex => async (project) => {
const initializeStore = (knex) => {
debug('Initializing store...')
return {
addProject: addProject(knex)
addProject: addProject(knex),
addGithubOrganization: addGithubOrganization(knex)
}
}

Expand Down

0 comments on commit 96dd002

Please sign in to comment.