Only this pageAll pages
Powered by GitBook
1 of 29

latest

Introduction

Dev docs for the Podkrepi.bg organisation.

Who is this documentation oriented for?

Our documentation is publicly accessible for everyone to read and contribute to. All current and future contributors are encouraged to read the Dev Guidelines section as a starting point.

Weekly dev meetings

Our weekly development meeting takes place every Saturday between 6pm - 7:30pm EEST (UTC+3) in the discord server of the organisation. All issues regarding development can be discussed during these meetings and everyone is welcome to join!

Dev Guidelines

Branching strategy

The general approach to branching used within the organisation.

Inherits the process from https://nvie.com/posts/a-successful-git-branching-model/​‌

​​🌞 Good branch names:‌

  • nice-kebab-cased-titles

  • fixes-footer-links

  • 4411290-setup-state-management-integration

  • feature/new-design

  • hotfix/db-connection

  • release-1.2.3

​​⛈ Bad branch names:‌

  • patch-1 - not enough context

  • camelCasedBranchNames - camelcase

  • PascalCasedBranchNames - pascal case

  • long-titles-above-80-chars-{.....} - too long

  • #58/something - shell understands it as comment

Branching model

Merges

​​​

​​​

Pull requests guidelines

Wondering how to write a good PR up to the organisation's standards?

Pull requests are official record of all changes we make. As an open source organisation PRs might be read from multiple people and not only the assigned reviewers. Future contributors are likely to search and come back to past PRs for a variety of reasons, therefore, it is vital that the important changes are not only in the code but also included in the description. Always think about the reviewers and potential future readers!

Think twice about the title and description before submitting for review

The title is what usually appears in the version control. Thus, it must be a full, clear and imperative sentence.

Bad title example:

  • Deleting API endpoint and adding new one- not specific enough, not imperative, bad wording.

Good title example:

  • Delete the /foo/bar REST endpoint and replace it with /foo/baz

The latter example describes the specific change made, improves the searchability and gives enough information to the reader which allows them to skim through the changes rather than spending more time going through the rest of the description.

Keep the body as informative as possible. Start with outlining the problem you are trying to solve/the feature you want to add.

All readers must get a perfect impression of what the change is and reason/motivation behind it. Think about any attachments that might be useful. Add screenshots if the changeset leads to visual difference in the frontend, add any external link/references to any design docs that might help the reader to better understand context of/changes in the PR. However, try to keep those to a minimal and outline as much as possible in the written description as any external resources can easily be deleted in the future or problems with access permissions might occur.

Do not skip any important information. Outline with clarity the alternative solutions and the reasons for avoiding those. Similarly, it is vital to explain any shortcomings of the approach that you took to solving the problem, what can possibly go wrong in the future and how we can possibly overcome those issues if you can think of any.

Keep the changeset as small as possible

10 lines of code = 10 issues, 500 lines of code = LGTM!... code reviews in a nutshell...

Your PR changeset size should be just right, i.e. one self-contained change per PR! Furthermore, you should strive to limit your changeset to 250-300 lines of code where possible (source, source). This is beneficial for both the reviewers and authors. As a reviewer you will be able to go through the changes much quicker and provide a better feedback as the quantity of changes you are reviewing is less and the amount of context needed is proportional to the size. Vice-versa, as an author you will get good feedback fast and hoping for less potentially introduced bugs. Moreover, changes you will be required to make upon receiving the feedback would be less and easier to do, i.e. you will have to do less work if something is rejected. Small PRs are also easier to design and the mergeability and simplicity of rolling back the changes increase substantially.

How to introduce smaller PRs?

Break down your issues!

For example: you want to add a new feature for creating a product, your changes will include a new public API endpoint and its backend implementation. Instead of dumping all of your changes in a massive PR, you can break down your issue similarly to:

  • A differential with any Database schema changes required.

  • A differential with any protobuf changes required.

  • Initial commit with stubs for the backend.

  • A series of differentials containing the backend implementation and respective unit tests.

  • A differential with any changes required to the public API.

Stacking PRs greatly improves the efficiency of the above process, you can see more examples how to this here.

Tests for new functionality must go in the same PR as the functional changes, do not try to reduce the changeset size by splitting unit tests from functional changes.

Do not mix changes - refactoring, bug fixing and enhancements must not go in the same differential.

Large PRs

It is inevitable that sometimes larger PRs will be needed - when refactoring code, code added by automation, deleting files etc. This is always fine as long as you have considered all options how to avoid the massive change but nothing seems to help.

Do not break the builds!

The system's functionality must always be intact after merging the changes. We understand it is impossible for this rule not to be broken ever. But, introducing PRs that intentionally add a breaking change (even for a short period of time until your subsequent PR is merged) is unacceptable! There are a number of ways to avoid those and if unsure, ask your friendly community members for how to approach the problem.

Sources

The (written) unwritten guide to pull requests

Optimal pull request size

Best Practices for Code Review

The anatomy of a perfect pull request

Google's Engineering Practices documentation

Starting a task

Definition of done

An unwritten contract signed by everyone in the organisation.

We use the definition of done to asses whether or not an enhancement can be considered to be complete (shippable to production).

Do not mix DoD with acceptance criteria.

DoD is our general framework to derive and end-to-end process which ensures every common piece of work meets the organisation's quality standards. Whereas, AC is a set of strictly defined targets for each individual (functional) piece of work that must be met before a task is considered to be complete.

Why is DoD important to us?

It is vital that every contributor in our organisation is aware of and understands our DoD. This helps us:

  • Complete all tasks without introducing any gaps in the process.

  • Follow with clarity where we are on our roadmap and what has been completed.

  • Know what is expected from every contributor.

  • Meet a certain quality of our work which leads to:

    • Lowering the amount of time spend for reworks.

    • Delivering on our promise to create a platform which makes a difference in our society.

Framework

The following is a general approach towards DoD, each team is encouraged to derive their own process suitable for the team's functionality in the organisation.

Development

  1. Code and unit tests for functionality completed.

  2. Assumption of meeting Acceptance Criteria.

  3. Premerge [if applicable] tests and code reviews passed.

  4. Postmerge [if applicable] tests passed.

  5. Project builds with no failures.

  6. Project deployed to dev environment.

  7. Initial QA performed by the developer. [if this step fails, go back to step 1]

  8. Feature documentation [if applicable] and tests documentation [if applicable] written.

  9. Feature QA'ed by a QA engineer against the acceptance criteria. [if this step fails, go back to step 1]

  10. Official documentation updated.

  11. Feature marked as completed.

Release

  1. All features included in the release are marked as completed. Where a feature is not yet completed, the feature is postponed for the next release.

  2. Update changelog and release documentation where applicable.

  3. Current environment is "green" - all unit, functional, integration, E2E and other possible tests are passing.

  4. Release acceptance criteria is met. [If this step fails, go back to step 1]

  5. QA functionality of the platform. [If this step fails, go back to step 1]

  6. QA if any experimental features are exposed to the end user. [If this step fails, go back to step 1]

  7. Promote the release to the next environment in the pipeline.

  8. Release successfully deployed to new environment. [If this step fails, go back to step 1]

  9. Release marked as completed.

Recognising Contributions

We're integrated with https://allcontributors.org/ bot

Comment on this issue, asking @all-contributors bot to add a contributor:

@all-contributors please add @<username> for <contributions>

<contribution>: See the Emoji Key (Contribution Types Reference) for a list of valid contribution types.

Architecture

Backend

Frontend

Guidelines

React Guidelines

Imports

A common way to sort the imports in the file is by their source: external, absolute, relative separated by an empty line. Each of those groups can be sorted by line length, but that's not super important.

import React, { useState } from 'react'
import { useTranslation } from 'next-i18next'

import Nav from 'components/layout/Nav'
import Layout from 'components/layout/Layout'

import SimpleForm from './SimpleForm'
import styles from './advanced.module.scss'

File structure

Inherits AirBnb naming convention https://github.com/airbnb/javascript/tree/master/react#naming

Use PascalCase for React components and camelCase for their instances

Naming convention react components

Pascal cased file names src/components/GenericForm.tsx

export default function GenericForm() {}

Filename and default component of the file should have the same name.

Naming convention non react components

Camel cased file names src/utils/hooks/useUser.ts

Naming convention folders

Lowercase kebab cased folders src/components/common/password-reset/ResetForm.tsx

Naming convention pages

Lowercase kebab cased files located in src/pages/sample-page.tsx which correspond to /sample-page url.

Types

The common convention is that the main type of the component's props is called after the component itself with suffix -Props. Prop types of AdvancedForm becomes AdvancedFormProps.

type AdvancedFormProps = React.PropsWithChildren({
  title?: string
  age?: number
})

export default function AdvancedForm({ title = 'Nice', children, age }: AdvancedFormProps) {
  return (
    <div title={title} data-age={age}>
      {children}
    </div>
  )
}

Components

Preferred export style 🌞

  • Nice IDE support and readability

export default function RegisterPage() {
  return <div>page</div>
}

Alternative export styles

  • Named function

    ⛅ Allows attaching static props to the function

    function RegisterPage() {
      return <div>page</div>
    }
    
    Register.getInitialProps = async (ctx) => {
      return { stars: 128 }
    }
    
    export default RegisterPage
  • Const arrow function

    🌞 Nice for locally defined components

    const RegisterForm = () => <form>page</form>
    
    export default function RegisterPage() {
      return <RegisterForm />
    }

    ⛅ Okay for default exports, but not preferred

    const RegisterPage = () => <form>page</form>
    
    export default RegisterPage
  • Unnamed arrow function ⛈️

    Discouraged

    https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md

    export default () => <div>page</div>
  • Class components ⛈️

    Discouraged as hooks cannot be used inside the class components

    class Page extends React.Component {
      render() {
        return <div>page</div>
      }
    }

Styles

There are three common ways to style a component:

Styles using the <Box /> component

Single component that inherits all sizing props from MUI https://material-ui.com/system/basics/#all-inclusive

🌞 Nice for quick layouts that should follow the theme

<Box component="nav" px={5} mt={2}>
  <a>{t('nav.forgottenPassword')}</p>
</Box>

⛅Not the best for custom scenarios with more than _six props passed to it. Use hooks instead

⛅ Not nice when the children have clear nesting structure of more than _three levels. Use hooks or scss instead

<Box component="nav" px={5} pb={12} mt={2} mb={4} lineHeight={2} letterSpacing={none} fontSize={20}>
  <Box component="span" px={5} pb={12} mt={2} mb={4} lineHeight={2} letterSpacing={none} fontSize={17}>
    <a>{t('nav.forgottenPassword')}</p>
  </Box>
  <Box component="span" px={5} pb={12} mt={2} mb={4} lineHeight={2} letterSpacing={none} fontSize={13}>
    <a>{t('nav.forgottenPassword')}</p>
  </Box>
</Box>

Styles using useStyles() hook

🌞 Nice for very specific styling that leverages theme methods and props

const useStyles = makeStyles((theme) =>
  createStyles({
    pageTitle: {
      display: 'flex',
      flexDirection: 'column',
      alignItems: 'center',
      padding: theme.spacing(4),
      margin: theme.spacing(5, 3, 4),
      color: theme.palette.secondary.main,
      backgroundColor: theme.palette.primary.main,
      '&:hover': {
        color: theme.palette.secondary.dark,
      },
    },
    // ...
  }),
)

export default function SomeBox() {
  const classes = useStyles()
  return (
    <Box className={classes.pageTitle}>
      <p>{t('nav.forgottenPassword')}</p>
    </Box>
  )
}

⛅ Too verbose for simple use cases, if it contains less than 2 css rules. Use Box instead

⛅ Not the best when dealing with styling of deep nested structures within the same component. Use scss instead

Styles using SCSS files

Next.js supports SCSS out of the box. Read more at https://nextjs.org/docs/basic-features/built-in-css-support#sass-support

File convention is based on a suffix .module.scss (ex. about.module.scss)

🌞 Nice when dealing with complex nested structures that are scoped in a single component. When dealing with sub-components we're not sure if some of the rules will be left unused.

@import 'styles/variables';

.page {
  color: $text-color;

  .nav {
    background-color: $nav-color;

    a {
      text-decoration: none;
      text-transform: uppercase;
    }
  }
}
import styles from './about.module.scss'

<Box className={styles.page}>
  <p>{t('nav.forgottenPassword')}</p>
</Box>

⛅ Too verbose for simple use cases, if it contains less than 2 css rules in a dedicated file. Use Box instead

@import 'styles/variables';

a {
  text-decoration: none;
}

⛈️ Cannot use theme support or theme variables Use hook instead

Translations (i18n)

Translation namespaces

Default namespace is called common and contains translations used on all pages (Layout, Nav, etc) and is stored at frontend/public/locales/{locale}/common.json

Namespaces (scopes, domains) are stored in separate json files at frontend/public/locales/{locale}/{namespace}.json One namespace can combine the translations keys from several pages with common reusable strings ex. auth scope collects keys for login and register pages.

Translation keys

It is preferred to use kebab-case for translation keys and extract another level of nesting when the common prefix of the keys is above 3 or makes sense to be separated as new keys might be added in the future.

  • Namespace is separated with :

  • Translation nesting levels are separated with .

  • Words in a translation key are separated with -

domain:pages.nested-level.another-nested-level.translation-key

{
  "cta": {
    "login": "Log In",
    "register": "Register",
    "send": "Send",
    "reset": "Reset"
  },
  "fields": {
    "email": "Email",
    "password": "Password",
    "confirm-password": "Confirm Password",
    "first-name": "First name",
    "last-name": "Last name"
  },
  "pages": {
    "forgotten-password": {
      "instructions": "To reset your password, please type your email address below.",
      "greeting": "Hello {{name}}!"
    }
  }
}

Usage in React

Usage of translation hook useTranslation is preferred over usage of <Trans /> component, whenever possible.

Usage in components

import { useTranslation } from 'next-i18next'

export default function CustomComponent() {
  const { t } = uxseTranslation()

  return (
    <div>
      <h1>{t('nav.custom-page')}</h1>
      <h2>{t('auth:pages.forgotten-password.greeting', { name: 'Interpolation' })}</h2>
      <p>{t('auth:pages.forgotten-password.instructions')}</p>
    </div>
  )
}

SSR preloading i18n in pages

import { GetStaticProps } from 'next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'

import Page from 'components/forgottenPassword/ForgottenPasswordPage'

export const getStaticProps: GetStaticProps = async ({ locale }) => ({
  props: {
    ...(await serverSideTranslations(locale ?? 'bg', ['common', 'auth'])), // List used namespaces
  },
})

export default Page

Forms

Form definition

import React from 'react'
import * as yup from 'yup'
import { useTranslation } from 'next-i18next'
import { Grid, TextField, Button } from '@material-ui/core'

import { AlertStore } from 'stores/AlertStore'
import useForm, { translateError, customValidators } from 'common/form/useForm'

export type FormData = {
  email: string
}

const validationSchema: yup.SchemaOf<FormData> = yup.object().defined().shape({
  email: yup.string().email().required(),
})

const defaults: FormData = {
  email: '',
}

export type MyFormProps = { initialValues?: FormData }

export default function MyForm({ initialValues = defaults }: MyFormProps) {
  const { t } = useTranslation()

  const { formik } = useForm({
    initialValues,
    validationSchema,
    onSubmit: (values) => {
      console.log(values)
    },
  })

  return (
    <form onSubmit={formik.handleSubmit}>
      <Grid container spacing={3}>
        <Grid item xs={12}>
          <TextField
            type="text"
            fullWidth
            label={t('auth:fields.email')}
            name="email"
            size="small"
            variant="outlined"
            autoFocus
            error={Boolean(formik.errors.email)}
            helperText={translateError(formik.errors.email)}
            value={formik.values.email}
            onBlur={formik.handleBlur}
            onChange={formik.handleChange}
          />
        </Grid>
        <Grid item xs={12}>
          <Button fullWidth type="submit" color="primary" variant="contained">
            {t('auth:cta.login')}
          </Button>
        </Grid>
      </Grid>
    </form>
  )
}

Form usage

<MyForm />

<MyForm initailValues={{email: 'test@example.com'}} />

Validation

Default translations

Simple strings are mapped directly to their respective translation

{
  "invalid": "Field is invalid",
  "required": "Required field"
}
setLocale({
  mixed: {
    default: 'validation:invalid',
    required: 'validation:required',
  },
  string: {
    email: 'validation:email',
  },
})

Default translations with interpolation

Complex translation keys are being evaluated upon translation

{
  "field-too-short": "Field should be at least {{min}} symbols",
  "field-too-long": "Field should be maximum {{max}} symbols"
}
setLocale({
  string: {
    min: ({ min }: { min: number }) => ({
      key: 'validation:field-too-short',
      values: { min },
    }),
    max: ({ max }: { max: number }) => ({
      key: 'validation:field-too-long',
      values: { max },
    }),
  },
})

Custom translations in validation schema

Commonly used translations with the same translation key

yup.string().min(6 customValidators.passwordMin)

Inline translations in validation schema

Custom translations with keys defined right next to the form

const validationSchema: yup.SchemaOf<FormData> = yup
  .object()
  .defined()
  .shape({
    password: yup.string().min(6, ({ min }) => ({
      key: 'validation:password-min',
      values: { min },
    })),
  })

Environments

.env

## General ##	
#############	

COMPOSE_PROJECT_NAME=podkrepi	
NODE_ENV=development # development, production	
TARGET_ENV=development # development, production	

## API ##	
#########	

API_URL=https://api.podkrepi.localhost/	

## APP ##	
#########	

APP_URL=http://localhost:3040	
APP_PORT=3040	

## Next Auth ##	
#############	

NEXTAUTH_URL=https://http://localhost:3040	
JWT_SECRET=!Change__Me!	

## Discord ##	
#############	

DISCORD_CLIENT_ID=	
DISCORD_CLIENT_SECRET=

Local Development

Local development

Setup local dev environment

git clone git@github.com:daritelska-platforma/frontend.git
cd frontend

# Symlink dev environment
ln -s .env.example .env

# Install dependencies
yarn

Start development server

yarn dev

Visit http://localhost:3040/

Start dev server via Docker Compose

Install the binary via https://docs.docker.com/compose/install/

Start the container in foreground

docker-compose up

Start the container in background

docker-compose up -d
docker-compose logs -f

Stop the docker container with docker-compose down

Linting

yarn lint
yarn lint:styles
yarn format
yarn type-check

Production

Production environment

Build frontend

yarn build

Build Docker image

docker build . \
    --file ./Dockerfile \
    --target production \
    --build-arg NODE_ENV=production

Infrastructure

Deployment to Kubernetes

Prerequisites

  • Install kubectl https://kubernetes.io/docs/tasks/tools/

  • Install kustomize https://kubectl.docs.kubernetes.io/installation/kustomize/

Frontend

Staging

You may trigger staging release at any point of time from the master branch by:

cd podkrepi.bg/frontend
kubectl apply -k manifests/overlays/development

This will update the deployment using ghcr.io/podkrepi-bg/frontend:master image

Production

Create new release

The following command will:

  • Bump the version in package.json

  • Tag the latest master branch

  • [postversion] Push local tags to the remote origin

  • Update the image newTag version in frontend/manifests/overlays/production/kustomization.yaml

  • Commit and push the manifest update

cd podkrepi.bg/frontend
kubectl apply -k manifests/overlays/production

Apply manifests

Once the image has been built by the GitHub Actions and is present in the Docker image repository you may trigger the actual deployment to the cluster.

https://github.com/orgs/podkrepi-bg/packages/container/package/frontend

cd podkrepi.bg/frontend
kubectl apply -k manifests/overlays/production

Backend

Staging

You may trigger staging release at any point of time from the master branch by:

cd podkrepi.bg/backend
kubectl apply -k manifests/overlays/development

This will update the deployment using ghcr.io/podkrepi-bg/api:master image

Production

Create new release

The following command will:

  • Bump the version in package.json

  • Tag the latest master branch

  • [postversion] Push local tags to the remote origin

  • Update the image newTag version in backend/manifests/overlays/production/kustomization.yaml

  • Commit and push the manifest update

cd podkrepi.bg/backend
kubectl apply -k manifests/overlays/production

Apply manifests

Once the image has been built by the GitHub Actions and is present in the Docker image repository you may trigger the actual deployment to the cluster.

https://github.com/orgs/podkrepi-bg/packages/container/package/api

cd podkrepi.bg/backend
kubectl apply -k manifests/overlays/production

Manual deployment

If you want to set a specific version for the deployment image you can do that by editing backend/manifests/overlays/production/kustomization.yaml

images:
- name: ghcr.io/podkrepi-bg/api/migrations
  newTag: v0.3.3
- name: ghcr.io/podkrepi-bg/api
  newTag: v0.3.3

REST API

Authentication

Endpoints

Healthcheck

Health check

GET https://api.podkrepi.localhost/api/v1/healthcheck

{
  "status": 200
}
{
  "status": 500
}

Contact Request

Create contact request

POST https://api.podkrepi.localhost/api/v1/contact

Request Body

Name
Type
Description

email

string

phone

string

company

string

message

string

firstName

string

lastName

string

{
  "id": "112cb853-b0b9-482e-a944-cf4fca5566b7",
  "firstName": "John",
  "lastName": "Doe",
  "email": "john.doe@example.com",
  "company": "Camplight",
  "phone": "+359888888888",
  "message": "I wanna help",
  "createdAt": "2021-03-19T00:36:50.076597738Z",
  "updatedAt": "2021-03-19T00:36:50.076597738Z",
  "deletedAt": null
}
{
  "statusCode": 400,
  "error": "email: non zero value required;firstName: non zero value required;lastName: non zero value required;message: non zero value required;phone: non zero value required",
  "validation": [
    {
      "field": "firstName",
      "message": "firstName: non zero value required",
      "validator": "required",
      "customMessage": false
    },
    {
      "field": "lastName",
      "message": "lastName: non zero value required",
      "validator": "required",
      "customMessage": false
    },
    {
      "field": "email",
      "message": "email: non zero value required",
      "validator": "required",
      "customMessage": false
    },
    {
      "field": "phone",
      "message": "phone: non zero value required",
      "validator": "required",
      "customMessage": false
    },
    {
      "field": "message",
      "message": "message: non zero value required",
      "validator": "required",
      "customMessage": false
    }
  ]
}

Fetch contact requests

GET https://api.podkrepi.localhost/api/v1/contact

This endpoint allows you to get free cakes.

Headers

Name
Type
Description

Authentication

string

Authentication token to track down who is emptying our stocks.

[
  {
    "id": "0d6ff16e-ae4c-48e4-868c-05095d923053",
    "firstName": "John",
    "lastName": "Doe",
    "email": "john.doe@example.com",
    "company": "Camplight",
    "phone": "",
    "message": "I wanna help",
    "createdAt": "2021-03-19T00:11:21.031966Z",
    "updatedAt": "2021-03-19T00:11:21.031966Z",
    "deletedAt": null
  },
  {
    "id": "76e6e2f7-b3f2-4df4-9185-cc44185e7c86",
    "firstName": "John",
    "lastName": "Doe",
    "email": "john.doe@example.com",
    "company": "ACME",
    "phone": "+359888888888",
    "message": "I wanna help",
    "createdAt": "2021-03-19T00:20:31.355736Z",
    "updatedAt": "2021-03-19T00:20:31.355736Z",
    "deletedAt": null
  }
]

Fetch specific contact request

GET https://api.podkrepi.localhost/api/v1/contact/:id

Path Parameters

Name
Type
Description

ID

string

Headers

Name
Type
Description

Authentication

string

{
  "id": "112cb853-b0b9-482e-a944-cf4fca5566b7",
  "firstName": "John",
  "lastName": "Doe",
  "email": "john.doe@example.com",
  "company": "Camplight",
  "phone": "+359888888888",
  "message": "I wanna help",
  "createdAt": "2021-03-19T00:36:50.076597738Z",
  "updatedAt": "2021-03-19T00:36:50.076597738Z",
  "deletedAt": null
}
{
  "error": "No contact found",
  "status": 404
}

Delete contact request

DELETE https://api.podkrepi.localhost/api/v1/contact/:id

Path Parameters

Name
Type
Description

id

string

Headers

Name
Type
Description

Authentication

string

{
  "status": 200
}
{
  "status": 404
}

Support Request

Create support request

POST https://api.podkrepi.localhost/api/v1/support-request

Request Body

Name
Type
Description

person

object

support_data

object

{
  "id": "d1451c92-0e86-463a-bf1c-2e4554a77f30",
  "person": {
    "email": "test@example.com",
    "name": "John Doe",
    "phone": "+3598777777777",
    "address": "6 John Doe Str",
    "terms": true,
    "newsletter": true
  },
  "support_data": {
    "roles": {
      "benefactor": true,
      "partner": true,
      "associationMember": false,
      "promoter": false,
      "volunteer": true
    },
    "benefactor": {
      "campaignBenefactor": true,
      "platformBenefactor": false
    },
    "partner": {
      "npo": false,
      "bussiness": true,
      "other": false,
      "otherText": "aaaaa"
    },
    "volunteer": {
      "backend": false,
      "frontend": true,
      "marketing": false,
      "designer": true,
      "projectManager": false,
      "devOps": true,
      "financesAndAccounts": true,
      "lawyer": false,
      "qa": false
    },
    "associationMember": {
      "isMember": true
    },
    "promoter": {
      "mediaPartner": false,
      "ambassador": false,
      "other": true,
      "otherText": "bbbbb"
    }
  },
  "createdAt": "2021-04-07T12:57:09.42898025Z",
  "updatedAt": "2021-04-07T12:57:09.42898025Z",
  "deletedAt": null
}
{
  "statusCode": 400,
  "error": "Person.email: non zero value required;Person.name: non zero value required;Person.phone: non zero value required",
  "validation": [
    {
      "field": "Person.email",
      "message": "Person.email: non zero value required",
      "validator": "required",
      "customMessage": false
    },
    {
      "field": "Person.name",
      "message": "Person.name: non zero value required",
      "validator": "required",
      "customMessage": false
    },
    {
      "field": "Person.phone",
      "message": "Person.phone: non zero value required",
      "validator": "required",
      "customMessage": false
    }
  ]
}

Fetch support requests

GET https://api.podkrepi.localhost/api/v1/support-request

This endpoint allows you to get free cakes.

Headers

Name
Type
Description

Authentication

string

Authentication token to track down who is emptying our stocks.

[
  {
    "id": "17222da9-b550-4e82-a6c6-88100bf54675",
    "person": {
      "email": "",
      "name": "",
      "phone": "",
      "address": "",
      "terms": false,
      "newsletter": false
    },
    "support_data": {
      "roles": {
        "benefactor": false,
        "partner": false,
        "associationMember": false,
        "promoter": false,
        "volunteer": false
      },
      "benefactor": {
        "campaignBenefactor": false,
        "platformBenefactor": false
      },
      "partner": {
        "npo": false,
        "bussiness": false,
        "other": false,
        "otherText": ""
      },
      "volunteer": {
        "backend": false,
        "frontend": false,
        "marketing": false,
        "designer": false,
        "projectManager": false,
        "devOps": false,
        "financesAndAccounts": false,
        "lawyer": false,
        "qa": false
      },
      "associationMember": {
        "isMember": false
      },
      "promoter": {
        "mediaPartner": false,
        "ambassador": false,
        "other": false,
        "otherText": ""
      }
    },
    "createdAt": "2021-04-07T09:16:28.026936Z",
    "updatedAt": "2021-04-07T09:16:28.026936Z",
    "deletedAt": null
  }
]

Fetch specific support request

GET https://api.podkrepi.localhost/api/v1/support-request/:id

Path Parameters

Name
Type
Description

ID

string

Headers

Name
Type
Description

Authentication

string

{
  "id": "17222da9-b550-4e82-a6c6-88100bf54675",
  "person": {
    "email": "",
    "name": "",
    "phone": "",
    "address": "",
    "terms": false,
    "newsletter": false
  },
  "support_data": {
    "roles": {
      "benefactor": false,
      "partner": false,
      "associationMember": false,
      "promoter": false,
      "volunteer": false
    },
    "benefactor": {
      "campaignBenefactor": false,
      "platformBenefactor": false
    },
    "partner": {
      "npo": false,
      "bussiness": false,
      "other": false,
      "otherText": ""
    },
    "volunteer": {
      "backend": false,
      "frontend": false,
      "marketing": false,
      "designer": false,
      "projectManager": false,
      "devOps": false,
      "financesAndAccounts": false,
      "lawyer": false,
      "qa": false
    },
    "associationMember": {
      "isMember": false
    },
    "promoter": {
      "mediaPartner": false,
      "ambassador": false,
      "other": false,
      "otherText": ""
    }
  },
  "createdAt": "2021-04-07T09:16:28.026936Z",
  "updatedAt": "2021-04-07T09:16:28.026936Z",
  "deletedAt": null
}
{
  "error": "No support request found",
  "status": 404
}

Delete support request

DELETE https://api.podkrepi.localhost/api/v1/support-request/:id

Path Parameters

Name
Type
Description

id

string

Headers

Name
Type
Description

Authentication

string

{
  "status": 200
}
{
  "status": 404
}

Campaigns

Get Campaigns

GET https://api.podkrepi.bg/v1/campaigns

This endpoint allows you to get campaigns.

Headers

Name
Type
Description

Authentication

string

Authentication token to track down who is emptying our stocks.

[
    {
        "id": "5572f770-a434-4ed8-9a91-d94474d26c8e"
        "name": "Campaign name"
    },
    {
        "id": "97692474-2482-460f-86ec-2b84954f989b"
        "name": "Campaign name"
    }
]
{
    "error": "Not Found"
}

Get Campaign by ID

GET https://api.podkrepi.bg/v1/campaigns/:id

This endpoint allows you to get campaigns.

Path Parameters

Name
Type
Description

id

string

ID of the cake to get, for free of course.

Headers

Name
Type
Description

Authentication

string

Authentication token to track down who is emptying our stocks.

{
    "id": "5572f770-a434-4ed8-9a91-d94474d26c8e"
    "name": "Campaign name"
}
{
    "error": "Not Found"
}

GRAPHQL

Schema