mirror of
https://github.com/Eugeny/tabby-web.git
synced 2025-06-09 05:59:53 +00:00
wip
This commit is contained in:
parent
0b0d711a08
commit
3de04221c2
@ -10,6 +10,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/animations": "^11.0.0",
|
"@angular/animations": "^11.0.0",
|
||||||
|
"@angular/cdk": "^12.1.3",
|
||||||
"@angular/common": "^11.0.0",
|
"@angular/common": "^11.0.0",
|
||||||
"@angular/compiler": "^11.0.0",
|
"@angular/compiler": "^11.0.0",
|
||||||
"@angular/compiler-cli": "^11.0.0",
|
"@angular/compiler-cli": "^11.0.0",
|
||||||
|
@ -8,6 +8,8 @@ export interface User {
|
|||||||
active_version: string
|
active_version: string
|
||||||
custom_connection_gateway: string|null
|
custom_connection_gateway: string|null
|
||||||
custom_connection_gateway_token: string|null
|
custom_connection_gateway_token: string|null
|
||||||
|
config_sync_token: string
|
||||||
|
github_username: string
|
||||||
is_pro: boolean
|
is_pro: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import { CommonModule } from '@angular/common'
|
|||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { RouterModule } from '@angular/router'
|
import { RouterModule } from '@angular/router'
|
||||||
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'
|
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'
|
||||||
|
import { ClipboardModule } from '@angular/cdk/clipboard'
|
||||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'
|
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'
|
||||||
import { AppComponent } from './components/app.component'
|
import { AppComponent } from './components/app.component'
|
||||||
import { MainComponent } from './components/main.component'
|
import { MainComponent } from './components/main.component'
|
||||||
@ -50,6 +51,7 @@ const ROUTES = [
|
|||||||
NgbDropdownModule,
|
NgbDropdownModule,
|
||||||
NgbModalModule,
|
NgbModalModule,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
|
ClipboardModule,
|
||||||
RouterModule.forRoot(ROUTES),
|
RouterModule.forRoot(ROUTES),
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
label Active config
|
label Active config
|
||||||
.title
|
.title
|
||||||
fa-icon([icon]='_configIcon')
|
fa-icon([icon]='_configIcon')
|
||||||
span.ms-2 {{configService.activeConfig.created_at|date:"medium"}}
|
span.ms-2 {{configService.activeConfig.name}}
|
||||||
|
|
||||||
button.btn.btn-semi.me-2((click)='configService.duplicateActiveConfig()')
|
button.btn.btn-semi.me-2((click)='configService.duplicateActiveConfig()')
|
||||||
fa-icon([icon]='_copyIcon', [fixedWidth]='true')
|
fa-icon([icon]='_copyIcon', [fixedWidth]='true')
|
||||||
@ -38,7 +38,7 @@
|
|||||||
(click)='selectConfig(config)'
|
(click)='selectConfig(config)'
|
||||||
)
|
)
|
||||||
fa-icon([icon]='_configIcon')
|
fa-icon([icon]='_configIcon')
|
||||||
span Config created at {{config.created_at|date:"medium"}}
|
span {{config.name}}
|
||||||
|
|
||||||
.py-3
|
.py-3
|
||||||
button.btn.btn-semi.w-100((click)='createNewConfig()')
|
button.btn.btn-semi.w-100((click)='createNewConfig()')
|
||||||
|
@ -15,7 +15,10 @@ class DemoConnector {
|
|||||||
|
|
||||||
async loadConfig (): Promise<string> {
|
async loadConfig (): Promise<string> {
|
||||||
return `{
|
return `{
|
||||||
recoverTabs: false
|
recoverTabs: false,
|
||||||
|
web: {
|
||||||
|
preventAccidentalTabClosure: false,
|
||||||
|
},
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,11 +4,29 @@
|
|||||||
.modal-body
|
.modal-body
|
||||||
.mb-3
|
.mb-3
|
||||||
h5 GitHub account
|
h5 GitHub account
|
||||||
a.btn.btn-info(href='/api/1/auth/social/login/github')
|
a.btn.btn-info(href='/api/1/auth/social/login/github', *ngIf='!user.github_username')
|
||||||
fa-icon([icon]='_githubIcon', [fixedWidth]='true')
|
fa-icon([icon]='_githubIcon', [fixedWidth]='true')
|
||||||
span Connect a GitHub account
|
span Connect a GitHub account
|
||||||
|
.alert.alert-success.d-flex(*ngIf='user.github_username')
|
||||||
|
fa-icon.me-2([icon]='_okIcon', [fixedWidth]='true')
|
||||||
|
div
|
||||||
|
div Connected as #[strong {{user.github_username}}]
|
||||||
|
div(*ngIf='user.is_sponsor') Thank you for supporting Tabby on GitHub!
|
||||||
|
|
||||||
.mb-3
|
.mb-3.mt-4
|
||||||
|
h5 Config sync
|
||||||
|
.d-flex.aling-items-stretch.mb-3
|
||||||
|
.form-floating.w-100
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
readonly,
|
||||||
|
[ngModel]='user.config_sync_token'
|
||||||
|
)
|
||||||
|
label Sync token for the Tabby app
|
||||||
|
button.btn.btn-dark([cdkCopyToClipboard]='user.config_sync_token')
|
||||||
|
fa-icon([icon]='_copyIcon', [fixedWidth]='true')
|
||||||
|
|
||||||
|
.mb-3.mt-4
|
||||||
h5 Connection gateway
|
h5 Connection gateway
|
||||||
.form-check.form-switch
|
.form-check.form-switch
|
||||||
input.form-check-input(
|
input.form-check-input(
|
||||||
|
@ -5,6 +5,7 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
|||||||
import { User } from '../api'
|
import { User } from '../api'
|
||||||
import { AppConnectorService } from '../services/appConnector.service'
|
import { AppConnectorService } from '../services/appConnector.service'
|
||||||
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||||
|
import { faCheck, faCopy } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'settings-modal',
|
selector: 'settings-modal',
|
||||||
@ -15,6 +16,8 @@ export class SettingsModalComponent {
|
|||||||
user: User
|
user: User
|
||||||
customGatewayEnabled = false
|
customGatewayEnabled = false
|
||||||
_githubIcon = faGithub
|
_githubIcon = faGithub
|
||||||
|
_copyIcon = faCopy
|
||||||
|
_okIcon = faCheck
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public appConnector: AppConnectorService,
|
public appConnector: AppConnectorService,
|
||||||
|
@ -19,12 +19,12 @@
|
|||||||
@import "~bootstrap/scss/button-group";
|
@import "~bootstrap/scss/button-group";
|
||||||
// @import "~bootstrap/scss/nav";
|
// @import "~bootstrap/scss/nav";
|
||||||
// @import "~bootstrap/scss/navbar";
|
// @import "~bootstrap/scss/navbar";
|
||||||
// @import "~bootstrap/scss/card";
|
@import "~bootstrap/scss/card";
|
||||||
// @import "~bootstrap/scss/accordion";
|
// @import "~bootstrap/scss/accordion";
|
||||||
// @import "~bootstrap/scss/breadcrum/b";
|
// @import "~bootstrap/scss/breadcrum/b";
|
||||||
// @import "~bootstrap/scss/pagination";
|
// @import "~bootstrap/scss/pagination";
|
||||||
@import "~bootstrap/scss/badge";
|
@import "~bootstrap/scss/badge";
|
||||||
// @import "~bootstrap/scss/alert";
|
@import "~bootstrap/scss/alert";
|
||||||
// @import "~bootstrap/scss/progress";
|
// @import "~bootstrap/scss/progress";
|
||||||
@import "~bootstrap/scss/list-group";
|
@import "~bootstrap/scss/list-group";
|
||||||
// @import "~bootstrap/scss/close";
|
// @import "~bootstrap/scss/close";
|
||||||
|
@ -191,3 +191,7 @@ $modal-content-border-width: 1px;
|
|||||||
|
|
||||||
$progress-bg: $table-bg;
|
$progress-bg: $table-bg;
|
||||||
$progress-height: 3px;
|
$progress-height: 3px;
|
||||||
|
|
||||||
|
$alert-bg-scale: 90%;
|
||||||
|
$alert-border-scale: 50%;
|
||||||
|
$alert-color-scale: 50%;
|
||||||
|
@ -17,7 +17,7 @@ from social_django.models import UserSocialAuth
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from .consumers import GatewayAdminConnection
|
from .consumers import GatewayAdminConnection
|
||||||
from .sponsors import get_sponsor_usernames
|
from .sponsors import check_is_sponsor, check_is_sponsor_cached
|
||||||
from .models import Config, Gateway, User
|
from .models import Config, Gateway, User
|
||||||
|
|
||||||
|
|
||||||
@ -45,6 +45,8 @@ class GatewaySerializer(ModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class ConfigSerializer(ModelSerializer):
|
class ConfigSerializer(ModelSerializer):
|
||||||
|
name = fields.CharField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Config
|
model = Config
|
||||||
read_only_fields = ('user', 'created_at', 'modified_at')
|
read_only_fields = ('user', 'created_at', 'modified_at')
|
||||||
@ -100,6 +102,7 @@ class AppVersionViewSet(ListModelMixin, GenericViewSet):
|
|||||||
class UserSerializer(ModelSerializer):
|
class UserSerializer(ModelSerializer):
|
||||||
id = fields.IntegerField()
|
id = fields.IntegerField()
|
||||||
is_pro = fields.SerializerMethodField()
|
is_pro = fields.SerializerMethodField()
|
||||||
|
is_sponsor = fields.SerializerMethodField()
|
||||||
github_username = fields.SerializerMethodField()
|
github_username = fields.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -110,16 +113,18 @@ class UserSerializer(ModelSerializer):
|
|||||||
'active_config',
|
'active_config',
|
||||||
'custom_connection_gateway',
|
'custom_connection_gateway',
|
||||||
'custom_connection_gateway_token',
|
'custom_connection_gateway_token',
|
||||||
|
'config_sync_token',
|
||||||
'is_pro',
|
'is_pro',
|
||||||
|
'is_sponsor',
|
||||||
'github_username',
|
'github_username',
|
||||||
)
|
)
|
||||||
read_only_fields = ('id', 'username')
|
read_only_fields = ('id', 'username')
|
||||||
|
|
||||||
def get_is_pro(self, obj):
|
def get_is_pro(self, obj):
|
||||||
username = self.get_github_username(obj)
|
return check_is_sponsor_cached(obj) or obj.force_pro
|
||||||
if not username:
|
|
||||||
return False
|
def get_is_sponsor(self, obj):
|
||||||
return username in get_sponsor_usernames()
|
return check_is_sponsor_cached(obj)
|
||||||
|
|
||||||
def get_github_username(self, obj):
|
def get_github_username(self, obj):
|
||||||
social_auth = UserSocialAuth.objects.filter(user=obj, provider='github').first()
|
social_auth = UserSocialAuth.objects.filter(user=obj, provider='github').first()
|
||||||
|
@ -32,8 +32,8 @@ class Migration(migrations.Migration):
|
|||||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
('active_version', models.CharField(max_length=32, null=True)),
|
('active_version', models.CharField(max_length=32, null=True)),
|
||||||
('custom_connection_gateway', models.CharField(max_length=255, null=True)),
|
('custom_connection_gateway', models.CharField(max_length=255, null=True, blank=True)),
|
||||||
('custom_connection_gateway_token', models.CharField(max_length=255, null=True)),
|
('custom_connection_gateway_token', models.CharField(max_length=255, null=True, blank=True)),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
('modified_at', models.DateTimeField(auto_now=True)),
|
('modified_at', models.DateTimeField(auto_now=True)),
|
||||||
],
|
],
|
||||||
|
29
tabby/app/migrations/0004_sync_token.py
Normal file
29
tabby/app/migrations/0004_sync_token.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import secrets
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def run_forward(apps, schema_editor):
|
||||||
|
for user in apps.get_model('app', 'User').objects.all():
|
||||||
|
user.config_sync_token = secrets.token_hex(64)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0003_auto_20210711_1855'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='config_sync_token',
|
||||||
|
field=models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
migrations.RunPython(run_forward, lambda _, __: None),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='config_sync_token',
|
||||||
|
field=models.CharField(max_length=255),
|
||||||
|
),
|
||||||
|
]
|
18
tabby/app/migrations/0005_user_force_pro.py
Normal file
18
tabby/app/migrations/0005_user_force_pro.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.3 on 2021-07-24 10:13
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0004_sync_token'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='force_pro',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
28
tabby/app/migrations/0006_config_name.py
Normal file
28
tabby/app/migrations/0006_config_name.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def run_forward(apps, schema_editor):
|
||||||
|
for config in apps.get_model('app', 'Config').objects.all():
|
||||||
|
config.name = f'Unnamed config ({config.created_at.date()})'
|
||||||
|
config.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0005_user_force_pro'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='config',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=255, null=True),
|
||||||
|
),
|
||||||
|
migrations.RunPython(run_forward, lambda _, __: None),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='config',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=255),
|
||||||
|
),
|
||||||
|
]
|
@ -1,26 +1,38 @@
|
|||||||
|
import secrets
|
||||||
|
from datetime import date
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.contrib.auth.signals import user_logged_in
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.db.models.signals import post_save
|
|
||||||
|
|
||||||
|
|
||||||
class Config(models.Model):
|
class Config(models.Model):
|
||||||
user = models.ForeignKey('app.User', related_name='configs', on_delete=models.CASCADE)
|
user = models.ForeignKey('app.User', related_name='configs', on_delete=models.CASCADE)
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
content = models.TextField(default='{}')
|
content = models.TextField(default='{}')
|
||||||
last_used_with_version = models.CharField(max_length=32, null=True)
|
last_used_with_version = models.CharField(max_length=32, null=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
modified_at = models.DateTimeField(auto_now=True)
|
modified_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.name:
|
||||||
|
self.name = f'Unnamed config ({date.today()})'
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
active_config = models.ForeignKey(Config, null=True, on_delete=models.SET_NULL, related_name='+')
|
active_config = models.ForeignKey(Config, null=True, on_delete=models.SET_NULL, related_name='+')
|
||||||
active_version = models.CharField(max_length=32, null=True)
|
active_version = models.CharField(max_length=32, null=True)
|
||||||
custom_connection_gateway = models.CharField(max_length=255, null=True, blank=True)
|
custom_connection_gateway = models.CharField(max_length=255, null=True, blank=True)
|
||||||
custom_connection_gateway_token = models.CharField(max_length=255, null=True, blank=True)
|
custom_connection_gateway_token = models.CharField(max_length=255, null=True, blank=True)
|
||||||
|
config_sync_token = models.CharField(max_length=255)
|
||||||
|
force_pro = models.BooleanField(default=False)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
modified_at = models.DateTimeField(auto_now=True)
|
modified_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.config_sync_token:
|
||||||
|
self.config_sync_token = secrets.token_hex(64)
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Gateway(models.Model):
|
class Gateway(models.Model):
|
||||||
host = models.CharField(max_length=255)
|
host = models.CharField(max_length=255)
|
||||||
|
@ -2,25 +2,34 @@ from django.conf import settings
|
|||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from gql import Client, gql
|
from gql import Client, gql
|
||||||
from gql.transport.requests import RequestsHTTPTransport
|
from gql.transport.requests import RequestsHTTPTransport
|
||||||
|
from social_django.models import UserSocialAuth
|
||||||
|
|
||||||
|
from .models import User
|
||||||
|
|
||||||
|
|
||||||
GQL_ENDPOINT = 'https://api.github.com/graphql'
|
GQL_ENDPOINT = 'https://api.github.com/graphql'
|
||||||
CACHE_KEY = 'cached-sponsors'
|
CACHE_KEY = 'cached-sponsors:%s'
|
||||||
|
|
||||||
|
|
||||||
def fetch_sponsor_usernames():
|
def check_is_sponsor(user: User) -> bool:
|
||||||
|
try:
|
||||||
|
token = user.social_auth.get(provider='github').extra_data.get('access_token')
|
||||||
|
except UserSocialAuth.DoesNotExist:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
return False
|
||||||
|
|
||||||
client = Client(
|
client = Client(
|
||||||
transport=RequestsHTTPTransport(
|
transport=RequestsHTTPTransport(
|
||||||
url=GQL_ENDPOINT,
|
url=GQL_ENDPOINT,
|
||||||
use_json=True,
|
use_json=True,
|
||||||
headers={
|
headers={
|
||||||
'Authorization': f'Bearer {settings.GITHUB_TOKEN}',
|
'Authorization': f'Bearer {token}',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
result = []
|
|
||||||
|
|
||||||
after = None
|
after = None
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@ -31,21 +40,17 @@ def fetch_sponsor_usernames():
|
|||||||
query = '''
|
query = '''
|
||||||
query {
|
query {
|
||||||
user (login: "eugeny") {
|
user (login: "eugeny") {
|
||||||
sponsorshipsAsMaintainer(%s, includePrivate: true) {
|
sponsorshipsAsSponsor(%s) {
|
||||||
pageInfo {
|
pageInfo {
|
||||||
startCursor
|
startCursor
|
||||||
hasNextPage
|
hasNextPage
|
||||||
endCursor
|
endCursor
|
||||||
}
|
}
|
||||||
|
totalRecurringMonthlyPriceInDollars
|
||||||
nodes {
|
nodes {
|
||||||
createdAt
|
sponsorable {
|
||||||
tier {
|
... on Organization { login }
|
||||||
monthlyPriceInDollars
|
... on User { login }
|
||||||
}
|
|
||||||
sponsor{
|
|
||||||
... on User {
|
|
||||||
login
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,18 +59,22 @@ def fetch_sponsor_usernames():
|
|||||||
''' % (params,)
|
''' % (params,)
|
||||||
|
|
||||||
response = client.execute(gql(query))
|
response = client.execute(gql(query))
|
||||||
after = response['user']['sponsorshipsAsMaintainer']['pageInfo']['endCursor']
|
info = response['user']['sponsorshipsAsSponsor']
|
||||||
nodes = response['user']['sponsorshipsAsMaintainer']['nodes']
|
after = info['pageInfo']['endCursor']
|
||||||
|
nodes = info['nodes']
|
||||||
if not len(nodes):
|
if not len(nodes):
|
||||||
break
|
break
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
if node['tier']['monthlyPriceInDollars'] >= settings.GITHUB_SPONSORS_MIN_PAYMENT:
|
if node['sponsorable']['login'].lower() not in settings.GITHUB_ELIGIBLE_SPONSORSHIPS:
|
||||||
result.append(node['sponsor']['login'])
|
continue
|
||||||
|
if info['totalRecurringMonthlyPriceInDollars'] >= settings.GITHUB_SPONSORS_MIN_PAYMENT:
|
||||||
|
return True
|
||||||
|
|
||||||
return result
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_sponsor_usernames():
|
def check_is_sponsor_cached(user: User) -> bool:
|
||||||
if not cache.get(CACHE_KEY):
|
cache_key = CACHE_KEY % user.id
|
||||||
cache.set(CACHE_KEY, fetch_sponsor_usernames(), timeout=30)
|
if not cache.get(cache_key):
|
||||||
return cache.get(CACHE_KEY)
|
cache.set(cache_key, check_is_sponsor(user), timeout=30)
|
||||||
|
return cache.get(cache_key)
|
||||||
|
@ -1,11 +1,44 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from tabby.app.models import User
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import hashers, logout, login
|
||||||
from pyga.requests import Tracker, Page, Session, Visitor
|
from pyga.requests import Tracker, Page, Session, Visitor
|
||||||
|
|
||||||
|
|
||||||
class GAMiddleware:
|
class BaseMiddleware:
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
|
|
||||||
|
|
||||||
|
class TokenMiddleware(BaseMiddleware):
|
||||||
|
def __call__(self, request):
|
||||||
|
token_value = None
|
||||||
|
if 'auth_token' in request.GET:
|
||||||
|
token_value = request.GET['auth_token']
|
||||||
|
if request.META.get('HTTP_AUTHORIZATION'):
|
||||||
|
token_type, *credentials = request.META['HTTP_AUTHORIZATION'].split()
|
||||||
|
if token_type == 'Bearer' and len(credentials):
|
||||||
|
token_value = credentials[0]
|
||||||
|
|
||||||
|
user = User.objects.filter(config_sync_token=token_value).first()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
request.session.save = lambda *args, **kwargs: None
|
||||||
|
setattr(user, 'backend', 'django.contrib.auth.backends.ModelBackend')
|
||||||
|
login(request, user)
|
||||||
|
setattr(request, '_dont_enforce_csrf_checks', True)
|
||||||
|
|
||||||
|
response = self.get_response(request)
|
||||||
|
|
||||||
|
if user:
|
||||||
|
response.set_cookie = lambda *args, **kwargs: None
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class GAMiddleware(BaseMiddleware):
|
||||||
|
def __init__(self, get_response):
|
||||||
|
super().__init__(get_response)
|
||||||
if settings.GA_ID:
|
if settings.GA_ID:
|
||||||
self.tracker = Tracker(settings.GA_ID, settings.GA_DOMAIN)
|
self.tracker = Tracker(settings.GA_ID, settings.GA_DOMAIN)
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ MIDDLEWARE = [
|
|||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'tabby.middleware.TokenMiddleware',
|
||||||
'tabby.middleware.GAMiddleware',
|
'tabby.middleware.GAMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -179,6 +180,8 @@ LOGIN_REDIRECT_URL = '/app'
|
|||||||
APP_DIST_PATH = Path(os.getenv('APP_DIST_PATH', BASE_DIR / 'app-dist'))
|
APP_DIST_PATH = Path(os.getenv('APP_DIST_PATH', BASE_DIR / 'app-dist'))
|
||||||
NPM_REGISTRY = os.getenv('NPM_REGISTRY', 'https://registry.npmjs.org').rstrip('/')
|
NPM_REGISTRY = os.getenv('NPM_REGISTRY', 'https://registry.npmjs.org').rstrip('/')
|
||||||
|
|
||||||
|
GITHUB_ELIGIBLE_SPONSORSHIPS = None
|
||||||
|
|
||||||
for key in [
|
for key in [
|
||||||
'SOCIAL_AUTH_GITHUB_KEY',
|
'SOCIAL_AUTH_GITHUB_KEY',
|
||||||
'SOCIAL_AUTH_GITHUB_SECRET',
|
'SOCIAL_AUTH_GITHUB_SECRET',
|
||||||
@ -191,7 +194,7 @@ for key in [
|
|||||||
'CONNECTION_GATEWAY_AUTH_CA',
|
'CONNECTION_GATEWAY_AUTH_CA',
|
||||||
'CONNECTION_GATEWAY_AUTH_CERTIFICATE',
|
'CONNECTION_GATEWAY_AUTH_CERTIFICATE',
|
||||||
'CONNECTION_GATEWAY_AUTH_KEY',
|
'CONNECTION_GATEWAY_AUTH_KEY',
|
||||||
'GITHUB_SPONSORS_USER',
|
'GITHUB_ELIGIBLE_SPONSORSHIPS',
|
||||||
'GITHUB_SPONSORS_MIN_PAYMENT',
|
'GITHUB_SPONSORS_MIN_PAYMENT',
|
||||||
'GITHUB_TOKEN',
|
'GITHUB_TOKEN',
|
||||||
'ENABLE_LOGIN',
|
'ENABLE_LOGIN',
|
||||||
@ -221,3 +224,8 @@ for key in [
|
|||||||
v = globals()[key]
|
v = globals()[key]
|
||||||
if v and not os.path.exists(v):
|
if v and not os.path.exists(v):
|
||||||
raise ValueError(f'{v} does not exist')
|
raise ValueError(f'{v} does not exist')
|
||||||
|
|
||||||
|
if GITHUB_ELIGIBLE_SPONSORSHIPS:
|
||||||
|
GITHUB_ELIGIBLE_SPONSORSHIPS = GITHUB_ELIGIBLE_SPONSORSHIPS.split(',')
|
||||||
|
else:
|
||||||
|
GITHUB_ELIGIBLE_SPONSORSHIPS = []
|
||||||
|
16
yarn.lock
16
yarn.lock
@ -9,6 +9,15 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.0.0"
|
tslib "^2.0.0"
|
||||||
|
|
||||||
|
"@angular/cdk@^12.1.3":
|
||||||
|
version "12.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-12.1.3.tgz#716d1a20040adf1b41335f9f7b8bf961758de5e6"
|
||||||
|
integrity sha512-uCWHk/PjddNJsdrmexasphWGbf4kYtYyhUCSd4HEBrIDjYz166MTVSr3FHgn/s8/tlVou7uTnaEZM+ILWoe2iQ==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.2.0"
|
||||||
|
optionalDependencies:
|
||||||
|
parse5 "^5.0.0"
|
||||||
|
|
||||||
"@angular/common@^11.0.0":
|
"@angular/common@^11.0.0":
|
||||||
version "11.2.14"
|
version "11.2.14"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/common/-/common-11.2.14.tgz#52887277b0ae0438e584f9ae97b417ee51a694b5"
|
resolved "https://registry.yarnpkg.com/@angular/common/-/common-11.2.14.tgz#52887277b0ae0438e584f9ae97b417ee51a694b5"
|
||||||
@ -3046,6 +3055,11 @@ parse-json@^2.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
error-ex "^1.2.0"
|
error-ex "^1.2.0"
|
||||||
|
|
||||||
|
parse5@^5.0.0:
|
||||||
|
version "5.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
|
||||||
|
integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==
|
||||||
|
|
||||||
parse5@^6.0.1:
|
parse5@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
||||||
@ -4271,7 +4285,7 @@ trim-newlines@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
glob "^7.1.2"
|
glob "^7.1.2"
|
||||||
|
|
||||||
tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0:
|
tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
|
||||||
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
|
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
|
||||||
|
Loading…
x
Reference in New Issue
Block a user