Transparency

These documents keep us focused, remind us why we exist and show all that Shasta Trades is organized, mission focused, and transparent.
  • A database is an organized collection of information
  • SQL is the most popular database programming language
  • A database schema defines how database information is organized (tables, rows, indexes, etc.)
    • All our data goes into an encrypted SQL database
    • The architecture of this database is a schema
  • We are running a trades school, a construction company and a grant-funded nonprofit all at once
  • Grantors don't just give money and walk away
    • They love financial reports and this schema helps us organize our data so we may provide these reports
  • Instead of using QuickBooks for money, Airtable for clients, and Google Sheets for hours, everything lives in one encrypted SQL database
  • Example queries we may run thanks to this schema:
    • Identify and restrict specific grant funds to specific liabilities
    • Tell an apprentice how many hours they have left until they reach their 4-year CSLB requirement
    • Identify how many low-income, medium-income and/or senior citizen households we've helped this quarter, for grants that love helping specific cohorts of our community
  • The security of our system does not rely uppon keeping our schema secret, we rely on best practice:
    • Encryption
    • Authorization
    • Authentication
    • SQL Injection Protection
  • Can lead to valuable feedback, improvements, and collaborative projects
  • Knowledgeable stakeholder, partners and customers may help us identify potential issues
  • Signals technical competence and a culture of openness which helps us recruit skilled volunteers and staff
  • Provides a verifiable commitment to transparency
    • Increases donor confidence and contributions
    • Funders love organizations that track impact rigorously
    • Our public schema shows that we have the infrastructure to report outcomes at a detailed level
  • If another organization starts up, our schema can serve as their template, which accelerates their impact and grows our network of like-minded organizations

Database Schema

Store all people in our system (students, mentors, customers, Trustees, Board members, employees, vendors, etc.)

FieldTypeNotes
idINTEGERPK, AI
firstNameTEXTNOT NULL
lastNameTEXTNOT NULL
isActiveBOOLEANNOT NULL, DEFAULT = 1

Store contact details for each Person

FieldTypeNotes
idINTEGERPK, AI
personIdINTEGERUNIQUE INDEX, NOT NULL, FK → Person(id), CASCADE DELETE
emailTEXTUNIQUE INDEX, NOT NULL
emailVerifiedBOOLEANDEFAULT = 0
sendNewsletterBOOLEANDEFAULT = 1
sendJobOpportunityEmailsBOOLEANDEFAULT = 0
phoneNumberTEXTNULLABLE
phoneNumberVerifiedBOOLEANDEFAULT = 0
sendJobOpportunityTextsBOOLEANDEFAULT = 0

Stores authentication details between a Person and our application

FieldTypeNotes
idINTEGERPK, AI
personIdINTEGERINDEX, NOT NULL, FK → Person(id), CASCADE DELETE
expiresAtDATETIMENOT NULL
createdAtDATETIMENOT NULL
ipAddressTEXTNOT NULL

Before a Session is created we send a Person an email w/ a MagicToken (passwordless / magic link authentication)

FieldTypeNotes
idINTEGERPK, AI
personIdINTEGERINDEX, NOT NULL, FK → Person(id), CASCADE DELETE
tokenHashTEXTINDEX, NOT NULL
expiresAtDATETIMENOT NULL
usedBOOLEANNOT NULL, DEFAULT = 0

Store messages that are filled out with our Contact Us website form

FieldTypeNotes
idINTEGERPK, AI
personIdINTEGERNOT NULL, FK → Person(id), CASCADE DELETE
messageTEXTNOT NULL
createdAtDATETIMENOT NULL, DEFAULT = NOW

Trades lookup table

FieldTypeNotes
idINTEGERPK, AI
valueTEXTNOT NULL
isActiveBOOLEANNOT NULL, DEFAULT = 1

Store all work projects

FieldTypeNotes
idINTEGERPK, AI
statusIdINTEGERINDEX, NOT NULL, FK → JobStatus(id)
descriptionTEXTNULLABLE
addressTEXTNOT NULL
createdAtDATETIMENOT NULL, DEFAULT = NOW

Junction table between Job & Trade

FieldTypeNotes
idINTEGERPK, AI
jobIdINTEGERNOT NULL, FK → Job(id), CASCADE DELETE
tradeIdINTEGERINDEX, NOT NULL, FK → Trade(id)
--UNIQUE(jobId, tradeId)

Junction table between Job & Person (Client)

FieldTypeNotes
idINTEGERPK, AI
jobIdINTEGERNOT NULL, FK → Job(id), CASCADE DELETE
clientIdINTEGERINDEX, NOT NULL, FK → Person(id), CASCADE DELETE
--UNIQUE(jobId, clientId)

Job status lookup table

FieldTypeNotes
idINTEGERPK, AI
valueTEXTNOT NULL
descriptionTEXTNOT NULL
isActiveBOOLEANNOT NULL, DEFAULT = 1

Store entries from our service request (job lead) form

FieldTypeNotes
idINTEGERPK, AI
personIdINTEGERNOT NULL, FK → Person(id), CASCADE DELETE
jobIdINTEGERUNIQUE INDEX, NULLABLE, FK → Job(id), CASCADE DELETE
statusIdINTEGERINDEX, NOT NULL, FK → LeadStatus(id)
descriptionTEXTNOT NULL
createdAtDATETIMENOT NULL, DEFAULT = NOW

Lead status lookup table

FieldTypeNotes
idINTEGERPK, AI
valueTEXTNOT NULL
isActiveBOOLEANNOT NULL, DEFAULT = 1

Junction table between Trade & JobLead

FieldTypeNotes
idINTEGERPK, AI
jobLeadIdINTEGERNOT NULL, FK → JobLead(id), CASCADE DELETE
tradeIdINTEGERINDEX, NOT NULL, FK → Trade(id)
--UNIQUE(tradeId, jobLeadId)

Store entries from our staff interest form

FieldTypeNotes
idINTEGERPK, AI
personIdINTEGERNOT NULL, FK → Person(id), CASCADE DELETE
statusIdINTEGERNOT NULL, FK → LeadStatus(id)
positionIdINTEGERNOT NULL, FK → StaffPosition(id)
createdAtDATETIMENOT NULL, DEFAULT = NOW

Staff position lookup table

FieldTypeNotes
idINTEGERPK, AI
valueTEXTNOT NULL
isActiveBOOLEANNOT NULL, DEFAULT = 1
isHiringBOOLEANNOT NULL, DEFAULT = 1

Junction table between Person & StaffPosition that also tracks the employment time and potential reason for ending the position

FieldTypeNotes
idINTEGERPK, AI
personIdINTEGERNOT NULL, FK → Person(id), CASCADE DELETE
positionIdINTEGERINDEX, NOT NULL, FK → StaffPosition(id)
endReasonIdINTEGERNULLABLE, FK → StaffEndReason(id)
startDateDATETIMENOT NULL
endDateDATETIMENULLABLE
--UNIQUE(personId, positionId)

Staff end reason lookup table

FieldTypeNotes
idINTEGERPK, AI
valueTEXTNOT NULL
isActiveBOOLEANNOT NULL, DEFAULT = 1

Stores objectives on our Kanban

FieldTypeNotes
idINTEGERPK, AI
columnIdINTEGERINDEX, NOT NULL, FK → ObjectiveColumn(id)
createdByINTEGERNOT NULL, FK → Person(id), CASCADE DELETE
titleTEXTUNQUE INDEX, NOT NULL
descriptionTEXT
orderDECIMALINDEX, NOT NULL
createdAtDATETIMENOT NULL, DEFAULT = NOW

Stores activity/events for an Objective

FieldTypeNotes
idINTEGERPK, AI
objectiveIdINTEGERNOT NULL, FK → Objective(id), CASCADE DELETE
typeIdINTEGERINDEX, NOT NULL, FK → ObjectiveActivityType(id)
actorIdINTEGERINDEX, FK → Person(id)
assigneeIdINTEGERINDEX, FK → Person(id),
fromColumnIdINTEGERFK → ObjectiveColumn(id)
toColumnIdINTEGERFK → ObjectiveColumn(id),
tagIdINTEGERINDEX, FK → ObjectiveTag(id),
commentIdINTEGERINDEX, FK → ObjectiveComment(id),
createdAtDATETIMEINDEX, NOT NULL, DEFAULT = NOW
--INDEX(objectiveId, createdAt)

Objective activity types lookup table

FieldTypeNotes
idINTEGERPK, AI
valueTEXTNOT NULL
isActiveBOOLEANNOT NULL, DEFAULT = 1

Junction table between Objective & Person

FieldTypeNotes
idINTEGERPK, AI
objectiveIdINTEGERNOT NULL, FK → Objective(id), CASCADE DELETE
personIdINTEGERINDEX, NOT NULL, FK → Person(id), CASCADE DELETE
--UNIQUE(objectiveId, personId)

Objective column (on Kanban) lookup table

FieldTypeNotes
idINTEGERPK, AI
valueTEXTNOT NULL
isActiveBOOLEANNOT NULL, DEFAULT = 1

Objective tag lookup table (more specific then ObjectiveColumn)

FieldTypeNotes
idINTEGERPK, AI
valueTEXTNOT NULL
isActiveBOOLEANNOT NULL, DEFAULT = 1
orderINTEGERNOT NULL

Junction table between Objective & ObjectiveTag

FieldTypeNotes
idINTEGERPK, AI
objectiveIdINTEGERNOT NULL, FK → Objective(id), CASCADE DELETE
tagIdINTEGERINDEX, NOT NULL, FK → ObjectiveTag(id)
--UNIQUE(objectiveId, tagId)

Store Objective comments

FieldTypeNotes
idINTEGERPK, AI
objectiveIdINTEGERINDEX, NOT NULL, FK → Objective(id), CASCADE DELETE
createdByINTEGERNOT NULL, FK → Person(id), CASCADE DELETE
valueTEXTNOT NULL
createdAtDATETIMENOT NULL, DEFAULT = NOW

Junction table between ObjectiveComment & Person. If someone is assigned to an Objective then DO NOT store an entry for them here. ObjectiveComment__Assignee is for notifying people about an ObjectiveComment that are NOT assigned to an Objective when we'd love for them to know about a comment.

FieldTypeNotes
idINTEGERPK, AI
commentIdINTEGERNOT NULL, FK → ObjectiveComment(id), CASCADE DELETE
personIdINTEGERINDEX, NOT NULL, FK → Person(id), CASCADE DELETE
--UNIQUE(commentId, personId)