Compare commits

..

72 Commits

Author SHA1 Message Date
f447611d6f 4.44.3
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 2m53s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 3m28s
Build-Release-Image / Merge-Images (push) Successful in 26s
Build-Release-Image / Create-Release (push) Successful in 10s
Build-Release-Image / Notify (push) Successful in 3s
2024-05-24 12:00:06 +01:00
b0a170dcb4 4.44.0
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m31s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 4m10s
Build-Release-Image / Merge-Images (push) Successful in 23s
Build-Release-Image / Create-Release (push) Successful in 10s
Build-Release-Image / Notify (push) Successful in 5s
2024-05-23 12:00:07 +01:00
d807c96f06 4.43.0
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m31s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 4m49s
Build-Release-Image / Merge-Images (push) Successful in 23s
Build-Release-Image / Create-Release (push) Successful in 30s
Build-Release-Image / Notify (push) Successful in 4s
2024-05-09 12:00:07 +01:00
22113148e2 4.42.2
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m25s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 4m39s
Build-Release-Image / Merge-Images (push) Successful in 1m6s
Build-Release-Image / Create-Release (push) Successful in 11s
Build-Release-Image / Notify (push) Successful in 4s
2024-04-10 17:23:11 +01:00
ee6bcad4f9 4.42.1
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m7s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 3m32s
Build-Release-Image / Merge-Images (push) Successful in 18s
Build-Release-Image / Create-Release (push) Successful in 14s
Build-Release-Image / Notify (push) Successful in 15s
2024-03-26 12:00:08 +00:00
42696112cb 4.42.0
All checks were successful
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 3m40s
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 4m7s
Build-Release-Image / Merge-Images (push) Successful in 22s
Build-Release-Image / Create-Release (push) Successful in 15s
Build-Release-Image / Notify (push) Successful in 17s
2024-03-19 12:00:09 +00:00
df611e4774 4.41.2
All checks were successful
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 3m24s
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m44s
Build-Release-Image / Merge-Images (push) Successful in 17s
Build-Release-Image / Create-Release (push) Successful in 11s
Build-Release-Image / Notify (push) Successful in 5s
2024-03-15 12:00:08 +00:00
cb216393a5 Merge pull request 'Correct docker package name' (#2) from fix-package-name-in-gitea-actions into main
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m21s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 4m23s
Build-Release-Image / Merge-Images (push) Successful in 21s
Build-Release-Image / Create-Release (push) Successful in 32s
Build-Release-Image / Notify (push) Successful in 4s
Reviewed-on: #2
2024-03-14 15:47:01 +00:00
a77c8d2a51 Correct docker package name 2024-03-14 15:46:44 +00:00
6ce4344005 4.41.0
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m35s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 4m50s
Build-Release-Image / Merge-Images (push) Successful in 19s
Build-Release-Image / Create-Release (push) Successful in 12s
Build-Release-Image / Notify (push) Successful in 21s
2024-03-14 12:00:08 +00:00
2f50662c5d 4.40.2
All checks were successful
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 3m24s
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 4m8s
Build-Release-Image / Merge-Images (push) Successful in 51s
Build-Release-Image / Create-Release (push) Successful in 22s
Build-Release-Image / Notify (push) Successful in 18s
2024-03-07 12:00:08 +00:00
810b59efec 4.40.1
All checks were successful
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 3m26s
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m48s
Build-Release-Image / Merge-Images (push) Successful in 20s
Build-Release-Image / Create-Release (push) Successful in 33s
Build-Release-Image / Notify (push) Successful in 20s
2024-03-05 12:00:09 +00:00
bfbcf567aa Merge pull request 'Replace Drone with Gitea Actions' (#1) from gitea-actions into main
Reviewed-on: #1
2024-03-04 13:42:58 +00:00
a505186051 Remove Drone 2024-03-04 13:38:57 +00:00
8fcca8571a Add Gitea Actions 2024-03-04 13:38:52 +00:00
1550507667 4.39.3
All checks were successful
continuous-integration/drone/tag Build is passing
2024-02-27 12:00:07 +00:00
757f153042 4.39.2
All checks were successful
continuous-integration/drone/tag Build is passing
2024-02-23 12:00:07 +00:00
a9f65bed60 4.39.1
All checks were successful
continuous-integration/drone/tag Build is passing
2024-02-20 12:00:07 +00:00
a8ca607581 4.38.3
All checks were successful
continuous-integration/drone/tag Build is passing
2024-02-14 12:00:07 +00:00
5b47bd1654 4.38.2
All checks were successful
continuous-integration/drone/tag Build is passing
2024-02-06 12:00:07 +00:00
e9faf93878 4.38.0
All checks were successful
continuous-integration/drone/tag Build is passing
2024-02-03 16:55:23 +00:00
0f60f7cec9 4.37.2
All checks were successful
continuous-integration/drone/tag Build is passing
2024-01-27 12:00:07 +00:00
3180034ff8 4.37.1
All checks were successful
continuous-integration/drone/tag Build is passing
2024-01-25 12:00:08 +00:00
b3ee67213d 4.37.0
All checks were successful
continuous-integration/drone/tag Build is passing
2024-01-18 12:00:07 +00:00
aeb34f8582 4.36.8
All checks were successful
continuous-integration/drone/tag Build is passing
2023-12-28 12:00:07 +00:00
2372b8f50f 4.36.7
All checks were successful
continuous-integration/drone/tag Build is passing
2023-12-21 12:00:09 +00:00
f3050b2ca0 4.36.6
All checks were successful
continuous-integration/drone/tag Build is passing
2023-12-17 14:56:57 +00:00
ebe941c8a5 4.36.5
All checks were successful
continuous-integration/drone/tag Build is passing
2023-11-30 12:00:09 +00:00
651b2dd52a 4.36.4 2023-11-22 12:00:09 +00:00
1c580cb6f7 4.36.3 2023-11-08 12:00:06 +00:00
21765ae9d8 4.35.6 2023-11-07 12:00:06 +00:00
d661a52f43 4.35.3
All checks were successful
continuous-integration/drone/tag Build is passing
2023-10-05 12:00:06 +01:00
45528ff81d 4.35.2
All checks were successful
continuous-integration/drone/tag Build is passing
2023-10-03 12:00:06 +01:00
6170fbf127 4.35.1
All checks were successful
continuous-integration/drone/tag Build is passing
2023-10-02 12:00:06 +01:00
c8ab76066b 4.35.0
All checks were successful
continuous-integration/drone/tag Build is passing
2023-09-29 12:00:06 +01:00
357d34a42b 4.34.4
All checks were successful
continuous-integration/drone/tag Build is passing
2023-08-31 12:00:06 +01:00
246754872d 4.34.3
All checks were successful
continuous-integration/drone/tag Build is passing
2023-08-29 20:20:00 +01:00
df59d73d66 4.34.2
All checks were successful
continuous-integration/drone/tag Build is passing
2023-08-25 12:00:05 +01:00
ff6d78f255 4.34.1
All checks were successful
continuous-integration/drone/tag Build is passing
2023-08-09 12:00:05 +01:00
d59fa5fe1c Update .drone.yml 2023-08-06 17:56:31 +00:00
de1fe02200 4.33.3
All checks were successful
continuous-integration/drone/tag Build is passing
2023-08-06 17:51:04 +01:00
439bfc5efd Update README.md 2023-08-06 16:04:57 +00:00
0a8a420850 Update README.md 2023-08-06 16:04:47 +00:00
d119e74c2f Update README.md 2023-08-06 16:04:41 +00:00
b5485429ef Remove provenance [CI SKIP] 2023-08-06 16:01:04 +00:00
f3a7900cbd 4.32.4
All checks were successful
continuous-integration/drone/tag Build is passing
2023-08-02 16:49:54 +01:00
0f91161ff3 4.32.1
All checks were successful
continuous-integration/drone/tag Build is passing
2023-07-12 11:00:04 +00:00
1da9a3f828 4.32.0
All checks were successful
continuous-integration/drone/tag Build is passing
2023-07-11 11:00:05 +00:00
167e56bc95 4.31.0
Some checks failed
continuous-integration/drone/tag Build is failing
2023-06-30 11:00:06 +00:00
c5a0d2d513 4.30.1
Some checks failed
continuous-integration/drone/tag Build is failing
2023-06-28 11:00:03 +00:00
25ebbaa7fd 4.30.0
Some checks failed
continuous-integration/drone/tag Build is failing
2023-06-27 11:00:04 +00:00
067d94841e 4.29.4
All checks were successful
continuous-integration/drone/tag Build is passing
2023-06-07 11:00:05 +00:00
804eec0c03 4.29.3
All checks were successful
continuous-integration/drone/tag Build is passing
2023-06-01 11:00:05 +00:00
651f3f1e9c 4.28.2
All checks were successful
continuous-integration/drone/tag Build is passing
2023-05-16 11:00:09 +00:00
fd988d6ef0 4.28.1
All checks were successful
continuous-integration/drone/tag Build is passing
2023-05-10 11:00:05 +00:00
da4a8cc979 4.27.0
All checks were successful
continuous-integration/drone/tag Build is passing
2023-04-25 11:00:05 +00:00
299da46abe 4.26.1
All checks were successful
continuous-integration/drone/tag Build is passing
2023-04-20 11:00:06 +00:00
1ad8294ec3 4.25.1
All checks were successful
continuous-integration/drone/tag Build is passing
2023-04-15 11:00:05 +00:00
f5de4a9624 4.24.0
All checks were successful
continuous-integration/drone/tag Build is passing
2023-04-11 11:00:05 +00:00
5501b033e8 4.23.0
All checks were successful
continuous-integration/drone/tag Build is passing
2023-03-24 12:00:07 +00:00
32a4b865ef 4.22.5
All checks were successful
continuous-integration/drone/tag Build is passing
2023-03-14 12:00:06 +00:00
5d5a23dd63 Update '.drone.yml' 2023-03-08 18:32:53 +00:00
03053d0e54 4.22.4
Some checks are pending
continuous-integration/drone/tag Build is running
2023-03-08 12:00:06 +00:00
4d70590d05 4.22.3
All checks were successful
continuous-integration/drone/tag Build is passing
2023-03-01 12:00:06 +00:00
bc879c10ae 4.22.2
All checks were successful
continuous-integration/drone/tag Build is passing
2023-02-16 12:00:05 +00:00
c6b237a004 Update 'README.md' 2023-02-10 13:00:46 +00:00
722979fe19 Update 'README.md' 2023-01-27 16:29:12 +00:00
b63ada023d Update '.drone.yml' 2023-01-27 16:26:22 +00:00
8b4e4e3a2b 4.22.0
All checks were successful
continuous-integration/drone/tag Build is passing
2023-01-17 12:00:04 +00:00
32465d1220 4.21.3
All checks were successful
continuous-integration/drone/tag Build is passing
2022-12-30 16:47:07 +00:00
98bae4c86c 4.21.3 2022-12-30 16:23:27 +00:00
7ff6cf2451 add drone 2022-12-30 15:35:10 +00:00
14 changed files with 52 additions and 198 deletions

View File

@ -26,11 +26,7 @@ from app.email_utils import (
) )
from app.errors import AliasInTrashError from app.errors import AliasInTrashError
from app.events.event_dispatcher import EventDispatcher from app.events.event_dispatcher import EventDispatcher
from app.events.generated.event_pb2 import ( from app.events.generated.event_pb2 import AliasDeleted, AliasStatusChange, EventContent
AliasDeleted,
AliasStatusChanged,
EventContent,
)
from app.log import LOG from app.log import LOG
from app.models import ( from app.models import (
Alias, Alias,
@ -472,10 +468,9 @@ def transfer_alias(alias, new_user, new_mailboxes: [Mailbox]):
def change_alias_status(alias: Alias, enabled: bool, commit: bool = False): def change_alias_status(alias: Alias, enabled: bool, commit: bool = False):
LOG.i(f"Changing alias {alias} enabled to {enabled}")
alias.enabled = enabled alias.enabled = enabled
event = AliasStatusChanged( event = AliasStatusChange(
alias_id=alias.id, alias_email=alias.email, enabled=enabled alias_id=alias.id, alias_email=alias.email, enabled=enabled
) )
EventDispatcher.send_event(alias.user, EventContent(alias_status_change=event)) EventDispatcher.send_event(alias.user, EventContent(alias_status_change=event))

View File

@ -25,7 +25,6 @@ from app.errors import (
ErrAddressInvalid, ErrAddressInvalid,
) )
from app.extensions import limiter from app.extensions import limiter
from app.log import LOG
from app.models import Alias, Contact, Mailbox, AliasMailbox from app.models import Alias, Contact, Mailbox, AliasMailbox
@ -186,7 +185,6 @@ def toggle_alias(alias_id):
return jsonify(error="Forbidden"), 403 return jsonify(error="Forbidden"), 403
alias_utils.change_alias_status(alias, enabled=not alias.enabled) alias_utils.change_alias_status(alias, enabled=not alias.enabled)
LOG.i(f"User {user} changed alias {alias} enabled status to {alias.enabled}")
Session.commit() Session.commit()
return jsonify(enabled=alias.enabled), 200 return jsonify(enabled=alias.enabled), 200

View File

@ -281,7 +281,6 @@ JOB_DELETE_MAILBOX = "delete-mailbox"
JOB_DELETE_DOMAIN = "delete-domain" JOB_DELETE_DOMAIN = "delete-domain"
JOB_SEND_USER_REPORT = "send-user-report" JOB_SEND_USER_REPORT = "send-user-report"
JOB_SEND_PROTON_WELCOME_1 = "proton-welcome-1" JOB_SEND_PROTON_WELCOME_1 = "proton-welcome-1"
JOB_SEND_ALIAS_CREATION_EVENTS = "send-alias-creation-events"
# for pagination # for pagination
PAGE_LIMIT = 20 PAGE_LIMIT = 20

View File

@ -1,22 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: event.proto # source: event.proto
# Protobuf Python Version: 5.27.0 # Protobuf Python Version: 5.26.1
"""Generated protocol buffer code.""" """Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
_runtime_version.Domain.PUBLIC,
5,
27,
0,
'',
'event.proto'
)
# @@protoc_insertion_point(imports) # @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default() _sym_db = _symbol_database.Default()
@ -24,27 +14,25 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x65vent.proto\x12\x12simplelogin_events\"(\n\x0fUserPlanChanged\x12\x15\n\rplan_end_time\x18\x01 \x01(\r\"\r\n\x0bUserDeleted\"Z\n\x0c\x41liasCreated\x12\x10\n\x08\x61lias_id\x18\x01 \x01(\r\x12\x13\n\x0b\x61lias_email\x18\x02 \x01(\t\x12\x12\n\nalias_note\x18\x03 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x04 \x01(\x08\"L\n\x12\x41liasStatusChanged\x12\x10\n\x08\x61lias_id\x18\x01 \x01(\r\x12\x13\n\x0b\x61lias_email\x18\x02 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x03 \x01(\x08\"5\n\x0c\x41liasDeleted\x12\x10\n\x08\x61lias_id\x18\x01 \x01(\r\x12\x13\n\x0b\x61lias_email\x18\x02 \x01(\t\"D\n\x10\x41liasCreatedList\x12\x30\n\x06\x65vents\x18\x01 \x03(\x0b\x32 .simplelogin_events.AliasCreated\"\x93\x03\n\x0c\x45ventContent\x12?\n\x10user_plan_change\x18\x01 \x01(\x0b\x32#.simplelogin_events.UserPlanChangedH\x00\x12\x37\n\x0cuser_deleted\x18\x02 \x01(\x0b\x32\x1f.simplelogin_events.UserDeletedH\x00\x12\x39\n\ralias_created\x18\x03 \x01(\x0b\x32 .simplelogin_events.AliasCreatedH\x00\x12\x45\n\x13\x61lias_status_change\x18\x04 \x01(\x0b\x32&.simplelogin_events.AliasStatusChangedH\x00\x12\x39\n\ralias_deleted\x18\x05 \x01(\x0b\x32 .simplelogin_events.AliasDeletedH\x00\x12\x41\n\x11\x61lias_create_list\x18\x06 \x01(\x0b\x32$.simplelogin_events.AliasCreatedListH\x00\x42\t\n\x07\x63ontent\"y\n\x05\x45vent\x12\x0f\n\x07user_id\x18\x01 \x01(\r\x12\x18\n\x10\x65xternal_user_id\x18\x02 \x01(\t\x12\x12\n\npartner_id\x18\x03 \x01(\r\x12\x31\n\x07\x63ontent\x18\x04 \x01(\x0b\x32 .simplelogin_events.EventContentb\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x65vent.proto\x12\x12simplelogin_events\"\'\n\x0eUserPlanChange\x12\x15\n\rplan_end_time\x18\x01 \x01(\r\"\r\n\x0bUserDeleted\"Z\n\x0c\x41liasCreated\x12\x10\n\x08\x61lias_id\x18\x01 \x01(\r\x12\x13\n\x0b\x61lias_email\x18\x02 \x01(\t\x12\x12\n\nalias_note\x18\x03 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x04 \x01(\x08\"K\n\x11\x41liasStatusChange\x12\x10\n\x08\x61lias_id\x18\x01 \x01(\r\x12\x13\n\x0b\x61lias_email\x18\x02 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x03 \x01(\x08\"5\n\x0c\x41liasDeleted\x12\x10\n\x08\x61lias_id\x18\x01 \x01(\r\x12\x13\n\x0b\x61lias_email\x18\x02 \x01(\t\"\xce\x02\n\x0c\x45ventContent\x12>\n\x10user_plan_change\x18\x01 \x01(\x0b\x32\".simplelogin_events.UserPlanChangeH\x00\x12\x37\n\x0cuser_deleted\x18\x02 \x01(\x0b\x32\x1f.simplelogin_events.UserDeletedH\x00\x12\x39\n\ralias_created\x18\x03 \x01(\x0b\x32 .simplelogin_events.AliasCreatedH\x00\x12\x44\n\x13\x61lias_status_change\x18\x04 \x01(\x0b\x32%.simplelogin_events.AliasStatusChangeH\x00\x12\x39\n\ralias_deleted\x18\x05 \x01(\x0b\x32 .simplelogin_events.AliasDeletedH\x00\x42\t\n\x07\x63ontent\"y\n\x05\x45vent\x12\x0f\n\x07user_id\x18\x01 \x01(\r\x12\x18\n\x10\x65xternal_user_id\x18\x02 \x01(\t\x12\x12\n\npartner_id\x18\x03 \x01(\r\x12\x31\n\x07\x63ontent\x18\x04 \x01(\x0b\x32 .simplelogin_events.EventContentb\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'event_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'event_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS: if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None DESCRIPTOR._loaded_options = None
_globals['_USERPLANCHANGED']._serialized_start=35 _globals['_USERPLANCHANGE']._serialized_start=35
_globals['_USERPLANCHANGED']._serialized_end=75 _globals['_USERPLANCHANGE']._serialized_end=74
_globals['_USERDELETED']._serialized_start=77 _globals['_USERDELETED']._serialized_start=76
_globals['_USERDELETED']._serialized_end=90 _globals['_USERDELETED']._serialized_end=89
_globals['_ALIASCREATED']._serialized_start=92 _globals['_ALIASCREATED']._serialized_start=91
_globals['_ALIASCREATED']._serialized_end=182 _globals['_ALIASCREATED']._serialized_end=181
_globals['_ALIASSTATUSCHANGED']._serialized_start=184 _globals['_ALIASSTATUSCHANGE']._serialized_start=183
_globals['_ALIASSTATUSCHANGED']._serialized_end=260 _globals['_ALIASSTATUSCHANGE']._serialized_end=258
_globals['_ALIASDELETED']._serialized_start=262 _globals['_ALIASDELETED']._serialized_start=260
_globals['_ALIASDELETED']._serialized_end=315 _globals['_ALIASDELETED']._serialized_end=313
_globals['_ALIASCREATEDLIST']._serialized_start=317 _globals['_EVENTCONTENT']._serialized_start=316
_globals['_ALIASCREATEDLIST']._serialized_end=385 _globals['_EVENTCONTENT']._serialized_end=650
_globals['_EVENTCONTENT']._serialized_start=388 _globals['_EVENT']._serialized_start=652
_globals['_EVENTCONTENT']._serialized_end=791 _globals['_EVENT']._serialized_end=773
_globals['_EVENT']._serialized_start=793
_globals['_EVENT']._serialized_end=914
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@ -1,11 +1,10 @@
from google.protobuf.internal import containers as _containers
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union
DESCRIPTOR: _descriptor.FileDescriptor DESCRIPTOR: _descriptor.FileDescriptor
class UserPlanChanged(_message.Message): class UserPlanChange(_message.Message):
__slots__ = ("plan_end_time",) __slots__ = ("plan_end_time",)
PLAN_END_TIME_FIELD_NUMBER: _ClassVar[int] PLAN_END_TIME_FIELD_NUMBER: _ClassVar[int]
plan_end_time: int plan_end_time: int
@ -27,7 +26,7 @@ class AliasCreated(_message.Message):
enabled: bool enabled: bool
def __init__(self, alias_id: _Optional[int] = ..., alias_email: _Optional[str] = ..., alias_note: _Optional[str] = ..., enabled: bool = ...) -> None: ... def __init__(self, alias_id: _Optional[int] = ..., alias_email: _Optional[str] = ..., alias_note: _Optional[str] = ..., enabled: bool = ...) -> None: ...
class AliasStatusChanged(_message.Message): class AliasStatusChange(_message.Message):
__slots__ = ("alias_id", "alias_email", "enabled") __slots__ = ("alias_id", "alias_email", "enabled")
ALIAS_ID_FIELD_NUMBER: _ClassVar[int] ALIAS_ID_FIELD_NUMBER: _ClassVar[int]
ALIAS_EMAIL_FIELD_NUMBER: _ClassVar[int] ALIAS_EMAIL_FIELD_NUMBER: _ClassVar[int]
@ -45,27 +44,19 @@ class AliasDeleted(_message.Message):
alias_email: str alias_email: str
def __init__(self, alias_id: _Optional[int] = ..., alias_email: _Optional[str] = ...) -> None: ... def __init__(self, alias_id: _Optional[int] = ..., alias_email: _Optional[str] = ...) -> None: ...
class AliasCreatedList(_message.Message):
__slots__ = ("events",)
EVENTS_FIELD_NUMBER: _ClassVar[int]
events: _containers.RepeatedCompositeFieldContainer[AliasCreated]
def __init__(self, events: _Optional[_Iterable[_Union[AliasCreated, _Mapping]]] = ...) -> None: ...
class EventContent(_message.Message): class EventContent(_message.Message):
__slots__ = ("user_plan_change", "user_deleted", "alias_created", "alias_status_change", "alias_deleted", "alias_create_list") __slots__ = ("user_plan_change", "user_deleted", "alias_created", "alias_status_change", "alias_deleted")
USER_PLAN_CHANGE_FIELD_NUMBER: _ClassVar[int] USER_PLAN_CHANGE_FIELD_NUMBER: _ClassVar[int]
USER_DELETED_FIELD_NUMBER: _ClassVar[int] USER_DELETED_FIELD_NUMBER: _ClassVar[int]
ALIAS_CREATED_FIELD_NUMBER: _ClassVar[int] ALIAS_CREATED_FIELD_NUMBER: _ClassVar[int]
ALIAS_STATUS_CHANGE_FIELD_NUMBER: _ClassVar[int] ALIAS_STATUS_CHANGE_FIELD_NUMBER: _ClassVar[int]
ALIAS_DELETED_FIELD_NUMBER: _ClassVar[int] ALIAS_DELETED_FIELD_NUMBER: _ClassVar[int]
ALIAS_CREATE_LIST_FIELD_NUMBER: _ClassVar[int] user_plan_change: UserPlanChange
user_plan_change: UserPlanChanged
user_deleted: UserDeleted user_deleted: UserDeleted
alias_created: AliasCreated alias_created: AliasCreated
alias_status_change: AliasStatusChanged alias_status_change: AliasStatusChange
alias_deleted: AliasDeleted alias_deleted: AliasDeleted
alias_create_list: AliasCreatedList def __init__(self, user_plan_change: _Optional[_Union[UserPlanChange, _Mapping]] = ..., user_deleted: _Optional[_Union[UserDeleted, _Mapping]] = ..., alias_created: _Optional[_Union[AliasCreated, _Mapping]] = ..., alias_status_change: _Optional[_Union[AliasStatusChange, _Mapping]] = ..., alias_deleted: _Optional[_Union[AliasDeleted, _Mapping]] = ...) -> None: ...
def __init__(self, user_plan_change: _Optional[_Union[UserPlanChanged, _Mapping]] = ..., user_deleted: _Optional[_Union[UserDeleted, _Mapping]] = ..., alias_created: _Optional[_Union[AliasCreated, _Mapping]] = ..., alias_status_change: _Optional[_Union[AliasStatusChanged, _Mapping]] = ..., alias_deleted: _Optional[_Union[AliasDeleted, _Mapping]] = ..., alias_create_list: _Optional[_Union[AliasCreatedList, _Mapping]] = ...) -> None: ...
class Event(_message.Message): class Event(_message.Message):
__slots__ = ("user_id", "external_user_id", "partner_id", "content") __slots__ = ("user_id", "external_user_id", "partner_id", "content")

View File

@ -102,7 +102,6 @@ class UnsubscribeHandler:
mailbox.email, alias mailbox.email, alias
): ):
return status.E509 return status.E509
LOG.i(f"User disabled alias {alias} via unsubscribe header")
alias_utils.change_alias_status(alias, enabled=False) alias_utils.change_alias_status(alias, enabled=False)
Session.commit() Session.commit()
enable_alias_url = config.URL + f"/dashboard/?highlight_alias_id={alias.id}" enable_alias_url = config.URL + f"/dashboard/?highlight_alias_id={alias.id}"

View File

@ -1,40 +0,0 @@
from app.events.event_dispatcher import EventDispatcher, Dispatcher
from app.events.generated.event_pb2 import EventContent, AliasCreated, AliasCreatedList
from app.log import LOG
from app.models import User, Alias
def send_alias_creation_events_for_user(
user: User, dispatcher: Dispatcher, chunk_size=50
):
if user.disabled:
LOG.i("User {user} is disabled. Skipping sending events for that user")
return
chunk_size = min(chunk_size, 50)
event_list = []
for alias in (
Alias.yield_per_query(chunk_size)
.filter_by(user_id=user.id)
.order_by(Alias.id.asc())
):
event_list.append(
AliasCreated(
alias_id=alias.id,
alias_email=alias.email,
alias_note=alias.note,
enabled=alias.enabled,
)
)
if len(event_list) >= chunk_size:
EventDispatcher.send_event(
user,
EventContent(alias_create_list=AliasCreatedList(events=event_list)),
dispatcher=dispatcher,
)
event_list = []
if len(event_list) > 0:
EventDispatcher.send_event(
user,
EventContent(alias_create_list=AliasCreatedList(events=event_list)),
dispatcher=dispatcher,
)

View File

@ -2,7 +2,6 @@ from newrelic import agent
from typing import Optional from typing import Optional
from app.db import Session from app.db import Session
from app.log import LOG
from app.errors import ProtonPartnerNotSetUp from app.errors import ProtonPartnerNotSetUp
from app.models import Partner, PartnerUser, User from app.models import Partner, PartnerUser, User
@ -31,7 +30,6 @@ def perform_proton_account_unlink(current_user: User):
user_id=current_user.id, partner_id=proton_partner.id user_id=current_user.id, partner_id=proton_partner.id
) )
if partner_user is not None: if partner_user is not None:
LOG.info(f"User {current_user} has unlinked the account from {partner_user}")
PartnerUser.delete(partner_user.id) PartnerUser.delete(partner_user.id)
Session.commit() Session.commit()
agent.record_custom_event("AccountUnlinked", {"partner": proton_partner.name}) agent.record_custom_event("AccountUnlinked", {"partner": proton_partner.name})

View File

@ -3,7 +3,7 @@ from requests import RequestException
from app import config from app import config
from app.events.event_dispatcher import EventDispatcher from app.events.event_dispatcher import EventDispatcher
from app.events.generated.event_pb2 import EventContent, UserPlanChanged from app.events.generated.event_pb2 import EventContent, UserPlanChange
from app.log import LOG from app.log import LOG
from app.models import User from app.models import User
@ -34,5 +34,5 @@ def execute_subscription_webhook(user: User):
except RequestException as e: except RequestException as e:
LOG.error(f"Subscription request exception: {e}") LOG.error(f"Subscription request exception: {e}")
event = UserPlanChanged(plan_end_time=sl_subscription_end) event = UserPlanChange(plan_end_time=sl_subscription_end)
EventDispatcher.send_event(user, EventContent(user_plan_change=event)) EventDispatcher.send_event(user, EventContent(user_plan_change=event))

View File

@ -15,7 +15,6 @@ from app.email_utils import (
render, render,
) )
from app.import_utils import handle_batch_import from app.import_utils import handle_batch_import
from app.jobs.event_jobs import send_alias_creation_events_for_user
from app.jobs.export_user_data_job import ExportUserDataJob from app.jobs.export_user_data_job import ExportUserDataJob
from app.log import LOG from app.log import LOG
from app.models import User, Job, BatchImport, Mailbox, CustomDomain, JobState from app.models import User, Job, BatchImport, Mailbox, CustomDomain, JobState
@ -198,18 +197,13 @@ def process_job(job: Job):
onboarding_mailbox(user) onboarding_mailbox(user)
elif job.name == config.JOB_ONBOARDING_4: elif job.name == config.JOB_ONBOARDING_4:
user_id = job.payload.get("user_id") user_id = job.payload.get("user_id")
user: User = User.get(user_id) user = User.get(user_id)
# user might delete their account in the meantime # user might delete their account in the meantime
# or disable the notification # or disable the notification
if user and user.notification and user.activated: if user and user.notification and user.activated:
# if user only has 1 mailbox which is Proton then do not send PGP onboarding email LOG.d("send onboarding pgp email to user %s", user)
mailboxes = user.mailboxes() onboarding_pgp(user)
if len(mailboxes) == 1 and mailboxes[0].is_proton():
LOG.d("Do not send onboarding PGP email to Proton mailbox")
else:
LOG.d("send onboarding pgp email to user %s", user)
onboarding_pgp(user)
elif job.name == config.JOB_BATCH_IMPORT: elif job.name == config.JOB_BATCH_IMPORT:
batch_import_id = job.payload.get("batch_import_id") batch_import_id = job.payload.get("batch_import_id")
@ -270,14 +264,8 @@ SimpleLogin team.
user_id = job.payload.get("user_id") user_id = job.payload.get("user_id")
user = User.get(user_id) user = User.get(user_id)
if user and user.activated: if user and user.activated:
LOG.d("Send proton welcome email to user %s", user) LOG.d("send proton welcome email to user %s", user)
welcome_proton(user) welcome_proton(user)
elif job.name == config.JOB_SEND_ALIAS_CREATION_EVENTS:
user_id = job.payload.get("user_id")
user = User.get(user_id)
if user and user.activated:
LOG.d(f"Sending alias creation events for {user}")
send_alias_creation_events_for_user(user)
else: else:
LOG.e("Unknown job name %s", job.name) LOG.e("Unknown job name %s", job.name)

28
app/poetry.lock generated
View File

@ -2150,22 +2150,24 @@ wcwidth = "*"
[[package]] [[package]]
name = "protobuf" name = "protobuf"
version = "5.27.1" version = "4.24.3"
description = "" description = ""
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.7"
files = [ files = [
{file = "protobuf-5.27.1-cp310-abi3-win32.whl", hash = "sha256:3adc15ec0ff35c5b2d0992f9345b04a540c1e73bfee3ff1643db43cc1d734333"}, {file = "protobuf-4.24.3-cp310-abi3-win32.whl", hash = "sha256:20651f11b6adc70c0f29efbe8f4a94a74caf61b6200472a9aea6e19898f9fcf4"},
{file = "protobuf-5.27.1-cp310-abi3-win_amd64.whl", hash = "sha256:25236b69ab4ce1bec413fd4b68a15ef8141794427e0b4dc173e9d5d9dffc3bcd"}, {file = "protobuf-4.24.3-cp310-abi3-win_amd64.whl", hash = "sha256:3d42e9e4796a811478c783ef63dc85b5a104b44aaaca85d4864d5b886e4b05e3"},
{file = "protobuf-5.27.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4e38fc29d7df32e01a41cf118b5a968b1efd46b9c41ff515234e794011c78b17"}, {file = "protobuf-4.24.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:6e514e8af0045be2b56e56ae1bb14f43ce7ffa0f68b1c793670ccbe2c4fc7d2b"},
{file = "protobuf-5.27.1-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:917ed03c3eb8a2d51c3496359f5b53b4e4b7e40edfbdd3d3f34336e0eef6825a"}, {file = "protobuf-4.24.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:ba53c2f04798a326774f0e53b9c759eaef4f6a568ea7072ec6629851c8435959"},
{file = "protobuf-5.27.1-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:ee52874a9e69a30271649be88ecbe69d374232e8fd0b4e4b0aaaa87f429f1631"}, {file = "protobuf-4.24.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:f6ccbcf027761a2978c1406070c3788f6de4a4b2cc20800cc03d52df716ad675"},
{file = "protobuf-5.27.1-cp38-cp38-win32.whl", hash = "sha256:7a97b9c5aed86b9ca289eb5148df6c208ab5bb6906930590961e08f097258107"}, {file = "protobuf-4.24.3-cp37-cp37m-win32.whl", hash = "sha256:1b182c7181a2891e8f7f3a1b5242e4ec54d1f42582485a896e4de81aa17540c2"},
{file = "protobuf-5.27.1-cp38-cp38-win_amd64.whl", hash = "sha256:f6abd0f69968792da7460d3c2cfa7d94fd74e1c21df321eb6345b963f9ec3d8d"}, {file = "protobuf-4.24.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b0271a701e6782880d65a308ba42bc43874dabd1a0a0f41f72d2dac3b57f8e76"},
{file = "protobuf-5.27.1-cp39-cp39-win32.whl", hash = "sha256:dfddb7537f789002cc4eb00752c92e67885badcc7005566f2c5de9d969d3282d"}, {file = "protobuf-4.24.3-cp38-cp38-win32.whl", hash = "sha256:e29d79c913f17a60cf17c626f1041e5288e9885c8579832580209de8b75f2a52"},
{file = "protobuf-5.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:39309898b912ca6febb0084ea912e976482834f401be35840a008da12d189340"}, {file = "protobuf-4.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:067f750169bc644da2e1ef18c785e85071b7c296f14ac53e0900e605da588719"},
{file = "protobuf-5.27.1-py3-none-any.whl", hash = "sha256:4ac7249a1530a2ed50e24201d6630125ced04b30619262f06224616e0030b6cf"}, {file = "protobuf-4.24.3-cp39-cp39-win32.whl", hash = "sha256:2da777d34b4f4f7613cdf85c70eb9a90b1fbef9d36ae4a0ccfe014b0b07906f1"},
{file = "protobuf-5.27.1.tar.gz", hash = "sha256:df5e5b8e39b7d1c25b186ffdf9f44f40f810bbcc9d2b71d9d3156fee5a9adf15"}, {file = "protobuf-4.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:f631bb982c5478e0c1c70eab383af74a84be66945ebf5dd6b06fc90079668d0b"},
{file = "protobuf-4.24.3-py3-none-any.whl", hash = "sha256:f6f8dc65625dadaad0c8545319c2e2f0424fede988368893ca3844261342c11a"},
{file = "protobuf-4.24.3.tar.gz", hash = "sha256:12e9ad2ec079b833176d2921be2cb24281fa591f0b119b208b788adc48c2561d"},
] ]
[[package]] [[package]]

View File

@ -2,7 +2,7 @@ syntax = "proto3";
package simplelogin_events; package simplelogin_events;
message UserPlanChanged { message UserPlanChange {
uint32 plan_end_time = 1; uint32 plan_end_time = 1;
} }
@ -16,7 +16,7 @@ message AliasCreated {
bool enabled = 4; bool enabled = 4;
} }
message AliasStatusChanged { message AliasStatusChange {
uint32 alias_id = 1; uint32 alias_id = 1;
string alias_email = 2; string alias_email = 2;
bool enabled = 3; bool enabled = 3;
@ -27,18 +27,13 @@ message AliasDeleted {
string alias_email = 2; string alias_email = 2;
} }
message AliasCreatedList {
repeated AliasCreated events = 1;
}
message EventContent { message EventContent {
oneof content { oneof content {
UserPlanChanged user_plan_change = 1; UserPlanChange user_plan_change = 1;
UserDeleted user_deleted = 2; UserDeleted user_deleted = 2;
AliasCreated alias_created = 3; AliasCreated alias_created = 3;
AliasStatusChanged alias_status_change = 4; AliasStatusChange alias_status_change = 4;
AliasDeleted alias_deleted = 5; AliasDeleted alias_deleted = 5;
AliasCreatedList alias_create_list = 6;
} }
} }
@ -47,4 +42,4 @@ message Event {
string external_user_id = 2; string external_user_id = 2;
uint32 partner_id = 3; uint32 partner_id = 3;
EventContent content = 4; EventContent content = 4;
} }

View File

@ -1,46 +0,0 @@
from app import config
from app.db import Session
from app.events.event_dispatcher import Dispatcher
from app.events.generated import event_pb2
from app.jobs.event_jobs import send_alias_creation_events_for_user
from app.models import Alias
from tests.utils import create_partner_linked_user
class MemStoreDispatcher(Dispatcher):
def __init__(self):
self.events = []
def send(self, event: bytes):
self.events.append(event)
def setup_module():
config.EVENT_WEBHOOK = True
def teardown_module():
config.EVENT_WEBHOOK = False
def test_send_alias_creation_events():
[user, partner_user] = create_partner_linked_user()
aliases = [Alias.create_new_random(user) for i in range(2)]
Session.flush()
dispatcher = MemStoreDispatcher()
send_alias_creation_events_for_user(user, dispatcher=dispatcher, chunk_size=2)
# 2 batches. 1st newsletter + first alias. 2nd last alias
assert len(dispatcher.events) == 2
decoded_event = event_pb2.Event.FromString(dispatcher.events[0])
assert decoded_event.user_id == user.id
assert decoded_event.external_user_id == partner_user.external_user_id
event_list = decoded_event.content.alias_create_list.events
assert len(event_list) == 2
# 0 is newsletter alias
assert event_list[1].alias_id == aliases[0].id
decoded_event = event_pb2.Event.FromString(dispatcher.events[1])
assert decoded_event.user_id == user.id
assert decoded_event.external_user_id == partner_user.external_user_id
event_list = decoded_event.content.alias_create_list.events
assert len(event_list) == 1
assert event_list[0].alias_id == aliases[1].id

View File

@ -9,8 +9,7 @@ from typing import Optional, Dict
import jinja2 import jinja2
from flask import url_for from flask import url_for
from app.models import User, PartnerUser from app.models import User
from app.proton.utils import get_proton_partner
from app.utils import random_string from app.utils import random_string
@ -31,18 +30,6 @@ def create_new_user(email: Optional[str] = None, name: Optional[str] = None) ->
return user return user
def create_partner_linked_user() -> tuple[User, PartnerUser]:
user = create_new_user()
partner_user = PartnerUser.create(
partner_id=get_proton_partner().id,
user_id=user.id,
external_user_id=random_token(10),
flush=True,
)
return user, partner_user
def login(flask_client, user: Optional[User] = None) -> User: def login(flask_client, user: Optional[User] = None) -> User:
if not user: if not user:
user = create_new_user() user = create_new_user()