Skip to content

Commit

Permalink
Environments (#51)
Browse files Browse the repository at this point in the history
* fix: misc fixes to schema

* feat: update graphql schema and types

* chore: update graphql schema export command

* fix: add identity key to env model

* fix: misc fixes and updates to schema

* chore: regenerate frontend schema and types

* feat: add frontend queries and mutations

* feat: crypto utils for asymmetric encryption

* feat: env key utils

* feat: add string encrypt and decrypt utils

* feat: add secrets page to app layout

* fix: env secret format

* fix: pss_env format

* feat: add secrets serializer

* feat: add util to infer env from token

* feat: add rest api for secrets

* fix: misc cleanup and fixes

* feat: pass requests on /secrets to backend

* fix: prefix and version ciphertexts

* fix: update wrapped value max length

* chore: format on save

* fix: rename env secret model to env token, add user token model

* chore: update types and schema to use environmen token

* chore: regenerate types and schema

* fix: update queries, mutations and utils for environment tokens

* feat: add util to generate user token

* feat: add schema, types and resolvers for user tokens

* chore: regenerate schema.graphql and types

* feat: query and mutation for user tokens

* feat: test user tokens

* feat: add user token kms api, misc fixes

* fix: add app context to env keys in token auth

* fix: use salt when hashing keys, encode hashes in hex

* fix: secrets api POST

* fix: secrets api DELETE

* fix: don't return deleted secrets in graphql resolver

* fix: secrets api PUT

* feat: modified user token response

* changed userId to snaky snakes

* refactor: frontend graphql setup

* feat: create input type for environment mutation

* feat: setup all three envs for app

* fix: default env names

* fix: reset version

* fix: dont return keys for deleted apps

* feat: user keyring context

* fix: check that all secrets to be deleted exist

* fix: order of checks

* fix: allow querying specific environment

* fix: misc fixes for secret mutations

* chore: regenerate types and schema

* fix: update default input style

* feat: add some utils

* feat: add queries and mutations

* feat: update secrets overvew page, add environment page

* fix: encrypting string util

* fix: create org operation name

* fix: update models and schema for tags

* feat: update secrets query for new tags schema

* feat: mutation and query to create and fetch tags

* fix: misc fixes for tags

* feat: allow querying email and username for org members

* fix: get secret history users

* feat: added encryption key to user token response

* feat: add warning button variant

* feat: misc ui improvements to keyring unlock dialog

* feat: misc ui improvements to secrets screen

* fix: use full screen width for secrets

* fix: misc improvements to secret row ui

* fix: active search query causes secret to appear modified

* fix: focus styles

* fix: misc fixes for light theme

* fix: correctly infer common secret names accross envs

* fix: col gap

* feat: add danger button variant

* feat: use danger button variant for deletes

* fix: use danger button variant for inline delete button

* fix: delete button light theme

* fix: input buttons overlapping text

* feat: create new secrets client side only, add polling

* fix: keep tags in single line

* fix: handle deleting new secrets that aren't deployed yet

* feat: secrets breadcrumbs

* fix: replace orange shades with amber

* feat: decrypt history

* feat: show changed properties in history dialog

* fix: input bg

* fix: input change handler, tag selector ui

* fix: remove unused util

* feat: service tokens

* fix: allow service tokens to fetch secrets

* fix: only check user access for user tokens

* fix: handle secret events for service tokens

* refactor: tokens tab

* fix: reset dialog state when closed

* fix: history log for service tokens

* fix: return 403 for revoked tokens

* feat: token expiry

* feat: update copy and text color for expired tokens

* fix: radiogroup comparison function

* test: print timestamps to debug

* fix: check for expired tokens in kms views

* fix: expiry check for tokens with no expiry

* fix: future timestamp math

* refactor: use single endpoint for secret tokens

* fix: misc ui / ux improvements to tokens screen

* fix: calculate token expiry when creating tokens

* fix: remove 2min expiry option

* feat: add invite model, schema, types and operations

* chore: regenerate schema and types

* feat: add invites query and mutation

* feat: add organisation context

* feat: increase free app limit to 5

* feat: add role to invite model

* feat: add invite validation query

* chore: regenerate schema and types

* feat: members page

* feat: invite page

* fix: misc tweaks to organisation context

* fix: redirect to callback url on signin

* fix: button titles

* feat: add apps, custom delete method to org member model

* feat: add create and delete org member mutations

* chore: regenerate graphql schema

* feat: create and delete org member mutations

* fix: input ring style

* fix: account for repeated words in list index

* fix: remove redundant border

* fix: restyle password strength bar

* feat: restyle dark mode toggle

* feat: new user onboarding flow

* feat: add delete handler and ui to members page

* fix: org member queries to account for soft deletes

* fix: org member queries

* feat: improve mode toggle styling

* fix: misc styling fixes to account password input

* fix: restyle sidebar

* fix: add icons to mode toggle in onboarding navbar

* feat: allow copying invite link from history, light theme fixes

* feat: add success pane

* fix: only return apps that a user has access too

* fix: icon button color

* fix: disable deploy button while save operation is in progress

* fix: update role mutation

* feat: update org member resolvers, add full name and avatar url

* chore: regenerate types

* feat: update role mutation

* feat: query user full name and avatar

* feat: add referrer to head to fix google avatar 403s

* feat: allow updating user roles inline, restyle user row

* feat: avatar component

* feat: show user fullname and avatar in history

* feat: user app management

* chore: remove commented code

* feat: simplified nginx config

* feat: only allow admins to update member access

* fix: only allow admins to delete users

* fix: set identity key for new env keys

* fix: hide app member form when all org memebrs are added

* fix: only show service tokens to admins

* fix: create keys for all admins when creating envs

* fix: disable action buttons for org owner

* refactor: move invites to members screen

* fix: vertical overflow

* fix: disable word wrapping for key names

* fix: misc tweaks to invites

* fix: remove member table bg

* fix: conditional logic to show app user action buttons

* fix: clean up console.log

* feat: grant user access to all envs when given admin role

* feat: better colors for alert

* feat: disallow changing env scope for admins

* feat: misc improvements to alert style

* fix: disable save button when user is admin

* fix: dialog layout

* fix: misc ui improvements

* fix: ui fixes to tokens dialog

* fix: misc ui fixes and cleanup

* feat: user tokens page, misc styling improvements

* fix: wire app new app dialog with keyring context

* feat: added SMTP config

* feat: added login alert email template

* feat: dispatch emails on sso based logins

* fix: graphql mutation operation name

* fix: operation name

* fix: veryify invite in useEffect hook

* Multi user (#57)

* fix: clean up console.log

* feat: grant user access to all envs when given admin role

* feat: better colors for alert

* feat: disallow changing env scope for admins

* feat: misc improvements to alert style

* fix: disable save button when user is admin

* fix: dialog layout

* fix: misc ui improvements

* fix: ui fixes to tokens dialog

* fix: misc ui fixes and cleanup

* feat: user tokens page, misc styling improvements

* fix: wire app new app dialog with keyring context

* fix: graphql mutation operation name

* fix: operation name

* fix: veryify invite in useEffect hook

---------

Co-authored-by: rohan-chaturvedi <[email protected]>

* refactor: emails

* feat: grab client user agent and ip during login

* fix: replace ph-backend with service

* fix: typos

* chore: removed applications from the license

* fix: get encrypted keyring from backend

* refactor: use org context to route user post login, correctly check local keyrings

* fix: re-arrange providers

* fix: validate org route param

* feat: add organisation list and loading state to org context

* feat: allow users with keyring on backend to login on new device with sudo pass

* fix: allow all user roles to invite new members

* fix: check local orgs by id and email

* fix: misc bugfixes

* feat: allow switching orgs

* fix: styling fixes to user menu

* fix: allow all users to revoke invites

* fix: invite dialog max width

* feat: encrypted recovery for new users

* feat: restyle org selection screen

* fix: invite dialog max width

* feat: allow skipping recovery step for invited users

* refactor: misc fixes, prevent duplicate local keyrings

* fix: refactor settings page, allow viewing account recovery

* fix: don't render account panel till activeOrg is truthy

* fix: move service tokens above user tokens

* feat: handle updating wrapped keyring and recovery

* fix: validate localkeyring before mutation

* fix: replace ad hoc queries with organisation context

* fix: invite link

* fix: check that apps is truthy before render

* feat: increased nginx proxy buffer sizes

* fix: not routing to onboard when logging in with no orgs

* fix: misc bug fixes and UI for account recovery

* fix: rename onboarding page to /signup

* fix: misc ui fixes to sidebar

* updated: signup and invite link recovery phase copy

* fix: user token sorting

* fix: user email for fallback when fullName is not available

* fix: add app to creator app list

* fix: wipe keyring context when switching between orgs

* fix: remove user tokens from app tokens tab

* feat: init app envs when creating app, correctly catch bad sudo pw exc

* feat: add self property to org member type

* fix: don't allow changing your own role when admin

* fix: don't allow non admins to delete apps

* fix: handle null selection in combobox

* fix: hide settings tab for devs

* fix: allow devs to view and create service tokens

* fix: rename tokens in sidebar to user tokens

* fix: render env listbox options in row

* fix: workspace selection light theme

* fix: horizontal attr for listbox

* feat: secrets ui improvements (#61)

* refactor: secrets overview page

* chore: regenerate apollo types

* feat: misc updates to secrets ui

* fix: remove shadows

* fix: bug when creating new secrets

* fix: correctly update comment state when clicking out of dialog

* fix: misc ui fixes

* fix: value field z-index

* fix: text area size

* fix: only show envs menu when multiple envs are available

* fix: add back logic to setup envs for legacy apps

* feat: create example secrets by default for new apps

* feat: add danger variant to alert

* feat: update delete app dialog copy and ui

* fix: misc cleanup

* fix: only show invalid input ring when group-focused

* feat: use secrets overview as app home

* feat: show active env in breadcrumbs

* fix: set starters switch default on only for first app

* fix: upsell copy

* feat: log read events with ip and user agent

* refactor: app logs

* fix: secret log paging

* fix: env key query params

* fix: only show KMS logs to org owners

* fix: remove secret fields from log, link to secret in env

* fix: bugs with app tabs

* fix: scroll highlighted secret into view

* fix: require one of envId or appId to query env keys

* fix: link to highlight secret in env from overview page

* fix: remove undefined query param

* refactor: app card

* fix: log when copying a value

* fix: rest api secret reads

* test: remove timestamp filter and sort from logs

* test: print exceptions

* test: print for debug

* fix: logs null return in cloud hosted mode

* fix: logs

* feat: invite emails

* feat: poll members and invites

* fix: hostname

* fix: invite link

* fix: email invite link

* fix: update email template

* fix: invite link button style

* feat: update starter secrets

* fix: don't show reads in secret history

* fix: rename user tokens to PATs

---------

Co-authored-by: rohan-chaturvedi <[email protected]>
  • Loading branch information
nimish-ks and rohan-chaturvedi authored Oct 9, 2023
1 parent 055b9f8 commit aac3f2c
Show file tree
Hide file tree
Showing 161 changed files with 15,110 additions and 2,259 deletions.
2 changes: 1 addition & 1 deletion .env.dev.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ HTTP_PROTOCOL=https://
NEXTAUTH_URL=https://localhost
OAUTH_REDIRECT_URI=https://localhost
BACKEND_API_BASE=http://backend:8000
NEXT_PUBLIC_BACKEND_API_BASE=https://localhost/ph-backend
NEXT_PUBLIC_BACKEND_API_BASE=https://localhost/service
NEXT_PUBLIC_NEXTAUTH_PROVIDERS=google,github,gitlab

# WARNING: Replace this with a cryptographically strong random value. You can use `openssl rand -hex 32` to generate this.
Expand Down
8 changes: 6 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"package-lock.json": true
},
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.formatOnSave": false,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": [
"source.addMissingImports",
"source.fixAll.eslint"
Expand All @@ -21,5 +21,9 @@
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"prettier.ignorePath": ".gitignore" // Don't run prettier for files listed in .gitignore
"prettier.ignorePath": ".gitignore",
"[python]": {
"editor.defaultFormatter": "ms-python.autopep8",
"editor.formatOnSave": true
} // Don't run prettier for files listed in .gitignore
}
4 changes: 2 additions & 2 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Phase Console - Backend

Python Django REST api + Postgres
Django + Graphene + DRF

### Generate graphql schema for frontend

```bash
./manage.py graphql_schema --schema backend.schema.schema --out ../dashboard/apollo/schema.graphql
./manage.py graphql_schema --schema backend.schema.schema --out ../frontend/apollo/schema.graphql
```
78 changes: 78 additions & 0 deletions backend/api/emails.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from django.conf import settings
from django.core.mail import send_mail
from django.template.loader import render_to_string
from datetime import datetime
import os
from api.utils import encode_string_to_base64, get_client_ip


def send_email(subject, recipient_list, template_name, context):
"""
Send email via SMTP gateway through Django's email backend.
"""
# Load the template
email_html_message = render_to_string(template_name, context)

# Get the DEFAULT_FROM_EMAIL from settings
default_from_email = getattr(settings, "DEFAULT_FROM_EMAIL")

# Send the email
send_mail(
subject,
'', # plain text content can be empty as we're sending HTML
default_from_email,
recipient_list,
html_message=email_html_message
)


def send_login_email(request, email):
user_agent = request.META.get('HTTP_USER_AGENT', 'Unknown')
ip_address = get_client_ip(request)
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

# Creating context dictionary
context = {
'auth': 'GitHub',
'email': email,
'ip': ip_address,
'user_agent': user_agent,
'timestamp': timestamp
}

send_email(
'New Login Alert - Phase Console',
[email],
'backend/api/email_templates/login.html',
context
)


def send_inite_email(invite):
organisation = invite.organisation.name

invited_by_social_acc = invite.invited_by.user.socialaccount_set.first()

name = invited_by_social_acc.extra_data.get('name')

if name is not None:
invited_by_name = name
else:
invited_by_name = invite.invited_by.user.email

invite_code = encode_string_to_base64(str(invite.id))

invite_link = f"{os.getenv('ALLOWED_ORIGINS')}/invite/{invite_code}"

context = {
'organisation': organisation,
'invited_by': invited_by_name,
'invite_link': invite_link
}

send_email(
f"Invite - {organisation} on Phase",
[invite.invitee_email],
'backend/api/email_templates/invite.html',
context
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Generated by Django 4.2.3 on 2023-07-31 10:52

from django.conf import settings
import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

dependencies = [
('api', '0016_organisation_plan'),
]

operations = [
migrations.CreateModel(
name='Environment',
fields=[
('id', models.TextField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('env_type', models.CharField(choices=[('dev', 'Development'), ('staging', 'Staging'), ('prod', 'Production')], default='dev', max_length=7)),
('wrapped_seed', models.CharField(max_length=208)),
('wrapped_salt', models.CharField(max_length=208)),
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('deleted_at', models.DateTimeField(blank=True, null=True)),
('is_deleted', models.BooleanField(default=False)),
('app', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.app')),
],
),
migrations.CreateModel(
name='Secret',
fields=[
('id', models.TextField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('collection', models.TextField(blank=True, null=True)),
('key', models.TextField()),
('key_digest', models.TextField()),
('value', models.TextField()),
('version', models.IntegerField(default=1)),
('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=64), size=10)),
('comment', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('deleted_at', models.DateTimeField(blank=True, null=True)),
('environment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.environment')),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='SecretTag',
fields=[
('id', models.TextField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('organisation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.organisation')),
],
),
migrations.CreateModel(
name='SecretEvent',
fields=[
('id', models.TextField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('collection', models.TextField(blank=True, null=True)),
('key', models.TextField()),
('key_digest', models.TextField()),
('value', models.TextField()),
('version', models.IntegerField(default=1)),
('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=64), size=10)),
('comment', models.TextField()),
('event_type', models.CharField(choices=[('C', 'Create'), ('R', 'Read'), ('U', 'Update'), ('D', 'Delete')], default='C', max_length=1)),
('timestamp', models.BigIntegerField()),
('environment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.environment')),
('secret', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.secret')),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='EnvironmentSecret',
fields=[
('id', models.TextField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('identity_key', models.CharField(max_length=256)),
('environment_token', models.CharField(max_length=64)),
('wrapped_key_share', models.CharField(max_length=406)),
('token', models.CharField(max_length=64)),
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('deleted_at', models.DateTimeField(blank=True, null=True)),
('environment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.environment')),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='EnvironmentKey',
fields=[
('id', models.TextField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('identity_key', models.CharField(max_length=256)),
('environment_token', models.CharField(max_length=64)),
('wrapped_seed', models.CharField(max_length=208)),
('wrapped_salt', models.CharField(max_length=208)),
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('deleted_at', models.DateTimeField(blank=True, null=True)),
('environment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.environment')),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Generated by Django 4.2.3 on 2023-08-01 07:57

from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

dependencies = [
('api', '0017_environment_secret_secrettag_secretevent_and_more'),
]

operations = [
migrations.RenameField(
model_name='environmentsecret',
old_name='environment_token',
new_name='name',
),
migrations.RemoveField(
model_name='environmentkey',
name='environment_token',
),
migrations.RemoveField(
model_name='secret',
name='collection',
),
migrations.AddField(
model_name='secrettag',
name='created_at',
field=models.DateTimeField(auto_now_add=True, null=True),
),
migrations.AddField(
model_name='secrettag',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='secrettag',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.CreateModel(
name='SecretFolder',
fields=[
('id', models.TextField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('deleted_at', models.DateTimeField(blank=True, null=True)),
('environment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.environment')),
('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.secretfolder')),
],
),
migrations.AddField(
model_name='secret',
name='folder',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='api.secretfolder'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 4.2.3 on 2023-08-02 07:03

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('api', '0018_rename_environment_token_environmentsecret_name_and_more'),
]

operations = [
migrations.RemoveField(
model_name='secret',
name='user',
),
migrations.RemoveField(
model_name='secretevent',
name='collection',
),
migrations.AddField(
model_name='secretevent',
name='folder',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='api.secretfolder'),
),
]
56 changes: 56 additions & 0 deletions backend/api/migrations/0020_remove_organisation_owner_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Generated by Django 4.2.3 on 2023-08-04 09:30

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid

def migrate_org_owners(apps, schema_editor):
OrgModel = apps.get_model('api', 'Organisation')
OrgMemberModel = apps.get_model('api', 'OrganisationMember')

for org in OrgModel.objects.all():
OrgMemberModel.objects.create(user=org.owner, organisation=org, role='owner', identity_key=org.identity_key, created_at=org.created_at)

class Migration(migrations.Migration):

dependencies = [
('api', '0019_remove_secret_user_remove_secretevent_collection_and_more'),
]

operations = [
migrations.CreateModel(
name='OrganisationMember',
fields=[
('id', models.TextField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('role', models.CharField(choices=[('owner', 'Owner'), ('admin', 'Admin'), ('dev', 'Developer')], default='dev', max_length=5)),
('identity_key', models.CharField(blank=True, max_length=256, null=True)),
('wrapped_keyring', models.TextField(blank=True)),
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('deleted_at', models.DateTimeField(auto_now=True)),
('organisation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='users', to='api.organisation')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organisation', to=settings.AUTH_USER_MODEL)),
],
),
migrations.AlterField(
model_name='environmentkey',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='api.organisationmember'),
),
migrations.AlterField(
model_name='environmentsecret',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='api.organisationmember'),
),
migrations.AlterField(
model_name='secretevent',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='api.organisationmember'),
),
migrations.RunPython(migrate_org_owners),
migrations.RemoveField(
model_name='organisation',
name='owner',
),
]
17 changes: 17 additions & 0 deletions backend/api/migrations/0021_remove_secretevent_timestamp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2.3 on 2023-08-04 09:42

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('api', '0020_remove_organisation_owner_and_more'),
]

operations = [
migrations.RemoveField(
model_name='secretevent',
name='timestamp',
),
]
Loading

0 comments on commit aac3f2c

Please sign in to comment.