Compare commits
139
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1cf0984c9 | ||
|
|
2956eadf87 | ||
|
|
7d940e1193 | ||
|
|
4767c0e75e | ||
|
|
14c2cea595 | ||
|
|
a2ce70d3f7 | ||
|
|
5e2bf1b326 | ||
|
|
89b8cdbe6e | ||
|
|
53e6a62a12 | ||
|
|
75f89c4c12 | ||
|
|
0983e5f44d | ||
|
|
2014400bed | ||
|
|
dffcc6dc10 | ||
|
|
640b23fb1b | ||
|
|
e13d8aa4b3 | ||
|
|
cf595de7c7 | ||
|
|
0fa9c8b015 | ||
|
|
b6db333fb7 | ||
|
|
6fe0415d66 | ||
|
|
ebc2bdcd3a | ||
|
|
11b222ece8 | ||
|
|
7eaa6d5431 | ||
|
|
a20cb55e9c | ||
|
|
c1a078ef3f | ||
|
|
9a5a9516b6 | ||
|
|
ad11a34efc | ||
|
|
9c28db8b7b | ||
|
|
32d5fea3d3 | ||
|
|
6adc721b34 | ||
|
|
531748dcc3 | ||
|
|
9965f48ba7 | ||
|
|
d15d7549f7 | ||
|
|
8fcffd2338 | ||
|
|
07e937cd8e | ||
|
|
1f1b421735 | ||
|
|
5a43ec8411 | ||
|
|
c86e7e02bc | ||
|
|
d91d7a2f60 | ||
|
|
b3b285b1e5 | ||
|
|
11debee402 | ||
|
|
1608e4f122 | ||
|
|
edeeee1227 | ||
|
|
9ff238816b | ||
|
|
6bd5caf008 | ||
|
|
c021aff58f | ||
|
|
683be55883 | ||
|
|
970ce8cf26 | ||
|
|
cbbe1a4e4d | ||
|
|
6a74677cc9 | ||
|
|
f6ea4f931d | ||
|
|
950fcfdb40 | ||
|
|
053c895120 | ||
|
|
245167e8aa | ||
|
|
6be3f0ea73 | ||
|
|
14d2d69120 | ||
|
|
570a9e319e | ||
|
|
469a027bb6 | ||
|
|
8c99a7df88 | ||
|
|
f34f078bd2 | ||
|
|
0fe2549bc6 | ||
|
|
118964de01 | ||
|
|
9f37f1ef0e | ||
|
|
986f29d1f2 | ||
|
|
1113f9fc19 | ||
|
|
ef3ec7274c | ||
|
|
a0a45b7916 | ||
|
|
2792d48288 | ||
|
|
b2ccca0567 | ||
|
|
2e822b38e4 | ||
|
|
e570fe404f | ||
|
|
48b613ae66 | ||
|
|
e70105235b | ||
|
|
7766e8b5cf | ||
|
|
16d63abcdc | ||
|
|
0568b8d583 | ||
|
|
64da29b0d9 | ||
|
|
7c336a65c4 | ||
|
|
2242a85e5c | ||
|
|
323920a358 | ||
|
|
151fc8389e | ||
|
|
0f828fd5e0 | ||
|
|
67cbe94d4a | ||
|
|
322af8c436 | ||
|
|
41c2aefad4 | ||
|
|
445c819fbd | ||
|
|
046a8a1bcf | ||
|
|
099a1cc12b | ||
|
|
a0a697401b | ||
|
|
cb92108bf4 | ||
|
|
01b685ea57 | ||
|
|
b16a585102 | ||
|
|
4a97d7c28c | ||
|
|
141cb17e8a | ||
|
|
26b62c4a70 | ||
|
|
e388a9a279 | ||
|
|
a3a580923c | ||
|
|
b4bc49971c | ||
|
|
04c7c53e09 | ||
|
|
78cc32765b | ||
|
|
4e485d6402 | ||
|
|
5a208cb1b9 | ||
|
|
0eafbb698a | ||
|
|
193ae9bfc8 | ||
|
|
7cb5a9120a | ||
|
|
84fc81dd98 | ||
|
|
2d0c0c7f8a | ||
|
|
5c9bdb1cea | ||
|
|
f8ca1e46b1 | ||
|
|
a3b9152a9b | ||
|
|
5223bd01e8 | ||
|
|
6eb0b5ddb0 | ||
|
|
cd200169b6 | ||
|
|
037bb88b53 | ||
|
|
643390e723 | ||
|
|
731c4e8fcd | ||
|
|
6216ad77f4 | ||
|
|
9812129ad3 | ||
|
|
5226b17f90 | ||
|
|
b376e5300a | ||
|
|
4460529b37 | ||
|
|
0a8cc24da5 | ||
|
|
2f4aa843fc | ||
|
|
cfac8ce350 | ||
|
|
75a11ba31a | ||
|
|
1fc3709731 | ||
|
|
7e21618762 | ||
|
|
2d475491e9 | ||
|
|
2a2feaf88e | ||
|
|
e48b2da623 | ||
|
|
9c9952a823 | ||
|
|
906ce8b500 | ||
|
|
6c483fad2f | ||
|
|
5b776392bd | ||
|
|
ba158d5d6e | ||
|
|
461e099bbc | ||
|
|
45e25ce18b | ||
|
|
4d88dbaf49 | ||
|
+4 |
e61ff879c4 | ||
|
|
adeb7d977d |
+2
-1
@@ -2,6 +2,7 @@
|
||||
*.pyc
|
||||
.env
|
||||
venv
|
||||
.venv
|
||||
node_modules/
|
||||
**/node_modules/
|
||||
npm-debug.log
|
||||
@@ -14,4 +15,4 @@ build/
|
||||
out/
|
||||
**/out/
|
||||
dist/
|
||||
**/dist/
|
||||
**/dist/
|
||||
|
||||
@@ -290,5 +290,6 @@ jobs:
|
||||
${{ github.workspace }}/deploy/selfhost/setup.sh
|
||||
${{ github.workspace }}/deploy/selfhost/swarm.sh
|
||||
${{ github.workspace }}/deploy/selfhost/restore.sh
|
||||
${{ github.workspace }}/deploy/selfhost/restore-airgapped.sh
|
||||
${{ github.workspace }}/deploy/selfhost/docker-compose.yml
|
||||
${{ github.workspace }}/deploy/selfhost/variables.env
|
||||
|
||||
+3
-2
@@ -53,6 +53,8 @@ mediafiles
|
||||
.env
|
||||
.DS_Store
|
||||
logs/
|
||||
htmlcov/
|
||||
.coverage
|
||||
|
||||
node_modules/
|
||||
assets/dist/
|
||||
@@ -73,8 +75,7 @@ package-lock.json
|
||||
|
||||
# lock files
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
pnpm-workspace.yaml
|
||||
yarn.lock
|
||||
|
||||
.npmrc
|
||||
.secrets
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
nodeLinker: node-modules
|
||||
+4
-4
@@ -69,14 +69,14 @@ chmod +x setup.sh
|
||||
docker compose -f docker-compose-local.yml up
|
||||
```
|
||||
|
||||
5. Start web apps:
|
||||
4. Start web apps:
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
6. Open your browser to http://localhost:3001/god-mode/ and register yourself as instance admin
|
||||
7. Open up your browser to http://localhost:3000 then log in using the same credentials from the previous step
|
||||
5. Open your browser to http://localhost:3001/god-mode/ and register yourself as instance admin
|
||||
6. Open up your browser to http://localhost:3000 then log in using the same credentials from the previous step
|
||||
|
||||
That’s it! You’re all set to begin coding. Remember to refresh your browser if changes don’t auto-reload. Happy contributing! 🎉
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "plane-api",
|
||||
"version": "0.26.0",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"description": "API server powering Plane's backend"
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
# Python imports
|
||||
import pytz
|
||||
from datetime import datetime
|
||||
|
||||
# Django imports
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.cache import cache_page
|
||||
|
||||
# Third party imports
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.views import APIView
|
||||
|
||||
# Module imports
|
||||
from plane.authentication.rate_limit import AuthenticationThrottle
|
||||
|
||||
|
||||
class TimezoneEndpoint(APIView):
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
throttle_classes = [AuthenticationThrottle]
|
||||
|
||||
@method_decorator(cache_page(60 * 60 * 2))
|
||||
def get(self, request):
|
||||
timezone_locations = [
|
||||
('Midway Island', 'Pacific/Midway'), # UTC-11:00
|
||||
('American Samoa', 'Pacific/Pago_Pago'), # UTC-11:00
|
||||
('Hawaii', 'Pacific/Honolulu'), # UTC-10:00
|
||||
('Aleutian Islands', 'America/Adak'), # UTC-10:00 (DST: UTC-09:00)
|
||||
('Marquesas Islands', 'Pacific/Marquesas'), # UTC-09:30
|
||||
('Alaska', 'America/Anchorage'), # UTC-09:00 (DST: UTC-08:00)
|
||||
('Gambier Islands', 'Pacific/Gambier'), # UTC-09:00
|
||||
('Pacific Time (US and Canada)', 'America/Los_Angeles'), # UTC-08:00 (DST: UTC-07:00)
|
||||
('Baja California', 'America/Tijuana'), # UTC-08:00 (DST: UTC-07:00)
|
||||
('Mountain Time (US and Canada)', 'America/Denver'), # UTC-07:00 (DST: UTC-06:00)
|
||||
('Arizona', 'America/Phoenix'), # UTC-07:00
|
||||
('Chihuahua, Mazatlan', 'America/Chihuahua'), # UTC-07:00 (DST: UTC-06:00)
|
||||
('Central Time (US and Canada)', 'America/Chicago'), # UTC-06:00 (DST: UTC-05:00)
|
||||
('Saskatchewan', 'America/Regina'), # UTC-06:00
|
||||
('Guadalajara, Mexico City, Monterrey', 'America/Mexico_City'), # UTC-06:00 (DST: UTC-05:00)
|
||||
('Tegucigalpa, Honduras', 'America/Tegucigalpa'), # UTC-06:00
|
||||
('Costa Rica', 'America/Costa_Rica'), # UTC-06:00
|
||||
('Eastern Time (US and Canada)', 'America/New_York'), # UTC-05:00 (DST: UTC-04:00)
|
||||
('Lima', 'America/Lima'), # UTC-05:00
|
||||
('Bogota', 'America/Bogota'), # UTC-05:00
|
||||
('Quito', 'America/Guayaquil'), # UTC-05:00
|
||||
('Chetumal', 'America/Cancun'), # UTC-05:00 (DST: UTC-04:00)
|
||||
('Caracas (Old Venezuela Time)', 'America/Caracas'), # UTC-04:30
|
||||
('Atlantic Time (Canada)', 'America/Halifax'), # UTC-04:00 (DST: UTC-03:00)
|
||||
('Caracas', 'America/Caracas'), # UTC-04:00
|
||||
('Santiago', 'America/Santiago'), # UTC-04:00 (DST: UTC-03:00)
|
||||
('La Paz', 'America/La_Paz'), # UTC-04:00
|
||||
('Manaus', 'America/Manaus'), # UTC-04:00
|
||||
('Georgetown', 'America/Guyana'), # UTC-04:00
|
||||
('Bermuda', 'Atlantic/Bermuda'), # UTC-04:00 (DST: UTC-03:00)
|
||||
('Newfoundland Time (Canada)', 'America/St_Johns'), # UTC-03:30 (DST: UTC-02:30)
|
||||
('Buenos Aires', 'America/Argentina/Buenos_Aires'), # UTC-03:00
|
||||
('Brasilia', 'America/Sao_Paulo'), # UTC-03:00
|
||||
('Greenland', 'America/Godthab'), # UTC-03:00 (DST: UTC-02:00)
|
||||
('Montevideo', 'America/Montevideo'), # UTC-03:00
|
||||
('Falkland Islands', 'Atlantic/Stanley'), # UTC-03:00
|
||||
('South Georgia and the South Sandwich Islands', 'Atlantic/South_Georgia'), # UTC-02:00
|
||||
('Azores', 'Atlantic/Azores'), # UTC-01:00 (DST: UTC+00:00)
|
||||
('Cape Verde Islands', 'Atlantic/Cape_Verde'), # UTC-01:00
|
||||
('Dublin', 'Europe/Dublin'), # UTC+00:00 (DST: UTC+01:00)
|
||||
('Reykjavik', 'Atlantic/Reykjavik'), # UTC+00:00
|
||||
('Lisbon', 'Europe/Lisbon'), # UTC+00:00 (DST: UTC+01:00)
|
||||
('Monrovia', 'Africa/Monrovia'), # UTC+00:00
|
||||
('Casablanca', 'Africa/Casablanca'), # UTC+00:00 (DST: UTC+01:00)
|
||||
('Central European Time (Berlin, Rome, Paris)', 'Europe/Paris'), # UTC+01:00 (DST: UTC+02:00)
|
||||
('West Central Africa', 'Africa/Lagos'), # UTC+01:00
|
||||
('Algiers', 'Africa/Algiers'), # UTC+01:00
|
||||
('Lagos', 'Africa/Lagos'), # UTC+01:00
|
||||
('Tunis', 'Africa/Tunis'), # UTC+01:00
|
||||
('Eastern European Time (Cairo, Helsinki, Kyiv)', 'Europe/Kiev'), # UTC+02:00 (DST: UTC+03:00)
|
||||
('Athens', 'Europe/Athens'), # UTC+02:00 (DST: UTC+03:00)
|
||||
('Jerusalem', 'Asia/Jerusalem'), # UTC+02:00 (DST: UTC+03:00)
|
||||
('Johannesburg', 'Africa/Johannesburg'), # UTC+02:00
|
||||
('Harare, Pretoria', 'Africa/Harare'), # UTC+02:00
|
||||
('Moscow Time', 'Europe/Moscow'), # UTC+03:00
|
||||
('Baghdad', 'Asia/Baghdad'), # UTC+03:00
|
||||
('Nairobi', 'Africa/Nairobi'), # UTC+03:00
|
||||
('Kuwait, Riyadh', 'Asia/Riyadh'), # UTC+03:00
|
||||
('Tehran', 'Asia/Tehran'), # UTC+03:30 (DST: UTC+04:30)
|
||||
('Abu Dhabi', 'Asia/Dubai'), # UTC+04:00
|
||||
('Baku', 'Asia/Baku'), # UTC+04:00 (DST: UTC+05:00)
|
||||
('Yerevan', 'Asia/Yerevan'), # UTC+04:00 (DST: UTC+05:00)
|
||||
('Astrakhan', 'Europe/Astrakhan'), # UTC+04:00
|
||||
('Tbilisi', 'Asia/Tbilisi'), # UTC+04:00
|
||||
('Mauritius', 'Indian/Mauritius'), # UTC+04:00
|
||||
('Islamabad', 'Asia/Karachi'), # UTC+05:00
|
||||
('Karachi', 'Asia/Karachi'), # UTC+05:00
|
||||
('Tashkent', 'Asia/Tashkent'), # UTC+05:00
|
||||
('Yekaterinburg', 'Asia/Yekaterinburg'), # UTC+05:00
|
||||
('Maldives', 'Indian/Maldives'), # UTC+05:00
|
||||
('Chagos', 'Indian/Chagos'), # UTC+05:00
|
||||
('Chennai', 'Asia/Kolkata'), # UTC+05:30
|
||||
('Kolkata', 'Asia/Kolkata'), # UTC+05:30
|
||||
('Mumbai', 'Asia/Kolkata'), # UTC+05:30
|
||||
('New Delhi', 'Asia/Kolkata'), # UTC+05:30
|
||||
('Sri Jayawardenepura', 'Asia/Colombo'), # UTC+05:30
|
||||
('Kathmandu', 'Asia/Kathmandu'), # UTC+05:45
|
||||
('Dhaka', 'Asia/Dhaka'), # UTC+06:00
|
||||
('Almaty', 'Asia/Almaty'), # UTC+06:00
|
||||
('Bishkek', 'Asia/Bishkek'), # UTC+06:00
|
||||
('Thimphu', 'Asia/Thimphu'), # UTC+06:00
|
||||
('Yangon (Rangoon)', 'Asia/Yangon'), # UTC+06:30
|
||||
('Cocos Islands', 'Indian/Cocos'), # UTC+06:30
|
||||
('Bangkok', 'Asia/Bangkok'), # UTC+07:00
|
||||
('Hanoi', 'Asia/Ho_Chi_Minh'), # UTC+07:00
|
||||
('Jakarta', 'Asia/Jakarta'), # UTC+07:00
|
||||
('Novosibirsk', 'Asia/Novosibirsk'), # UTC+07:00
|
||||
('Krasnoyarsk', 'Asia/Krasnoyarsk'), # UTC+07:00
|
||||
('Beijing', 'Asia/Shanghai'), # UTC+08:00
|
||||
('Singapore', 'Asia/Singapore'), # UTC+08:00
|
||||
('Perth', 'Australia/Perth'), # UTC+08:00
|
||||
('Hong Kong', 'Asia/Hong_Kong'), # UTC+08:00
|
||||
('Ulaanbaatar', 'Asia/Ulaanbaatar'), # UTC+08:00
|
||||
('Palau', 'Pacific/Palau'), # UTC+08:00
|
||||
('Eucla', 'Australia/Eucla'), # UTC+08:45
|
||||
('Tokyo', 'Asia/Tokyo'), # UTC+09:00
|
||||
('Seoul', 'Asia/Seoul'), # UTC+09:00
|
||||
('Yakutsk', 'Asia/Yakutsk'), # UTC+09:00
|
||||
('Adelaide', 'Australia/Adelaide'), # UTC+09:30 (DST: UTC+10:30)
|
||||
('Darwin', 'Australia/Darwin'), # UTC+09:30
|
||||
('Sydney', 'Australia/Sydney'), # UTC+10:00 (DST: UTC+11:00)
|
||||
('Brisbane', 'Australia/Brisbane'), # UTC+10:00
|
||||
('Guam', 'Pacific/Guam'), # UTC+10:00
|
||||
('Vladivostok', 'Asia/Vladivostok'), # UTC+10:00
|
||||
('Tahiti', 'Pacific/Tahiti'), # UTC+10:00
|
||||
('Lord Howe Island', 'Australia/Lord_Howe'), # UTC+10:30 (DST: UTC+11:00)
|
||||
('Solomon Islands', 'Pacific/Guadalcanal'), # UTC+11:00
|
||||
('Magadan', 'Asia/Magadan'), # UTC+11:00
|
||||
('Norfolk Island', 'Pacific/Norfolk'), # UTC+11:00
|
||||
('Bougainville Island', 'Pacific/Bougainville'), # UTC+11:00
|
||||
('Chokurdakh', 'Asia/Srednekolymsk'), # UTC+11:00
|
||||
('Auckland', 'Pacific/Auckland'), # UTC+12:00 (DST: UTC+13:00)
|
||||
('Wellington', 'Pacific/Auckland'), # UTC+12:00 (DST: UTC+13:00)
|
||||
('Fiji Islands', 'Pacific/Fiji'), # UTC+12:00 (DST: UTC+13:00)
|
||||
('Anadyr', 'Asia/Anadyr'), # UTC+12:00
|
||||
('Chatham Islands', 'Pacific/Chatham'), # UTC+12:45 (DST: UTC+13:45)
|
||||
("Nuku'alofa", 'Pacific/Tongatapu'), # UTC+13:00
|
||||
('Samoa', 'Pacific/Apia'), # UTC+13:00 (DST: UTC+14:00)
|
||||
('Kiritimati Island', 'Pacific/Kiritimati') # UTC+14:00
|
||||
]
|
||||
|
||||
timezone_list = []
|
||||
now = datetime.now()
|
||||
|
||||
# Process timezone mapping
|
||||
for friendly_name, tz_identifier in timezone_locations:
|
||||
|
||||
try:
|
||||
tz = pytz.timezone(tz_identifier)
|
||||
current_offset = now.astimezone(tz).strftime("%z")
|
||||
|
||||
# converting and formatting UTC offset to GMT offset
|
||||
current_utc_offset = now.astimezone(tz).utcoffset()
|
||||
total_seconds = int(current_utc_offset.total_seconds())
|
||||
hours_offset = total_seconds // 3600
|
||||
minutes_offset = abs(total_seconds % 3600) // 60
|
||||
offset = (
|
||||
f"{'+' if hours_offset >= 0 else '-'}"
|
||||
f"{abs(hours_offset):02}:{minutes_offset:02}"
|
||||
)
|
||||
|
||||
timezone_value = {
|
||||
"offset": int(current_offset),
|
||||
"utc_offset": f"UTC{offset}",
|
||||
"gmt_offset": f"GMT{offset}",
|
||||
"value": tz_identifier,
|
||||
"label": f"{friendly_name}",
|
||||
}
|
||||
|
||||
timezone_list.append(timezone_value)
|
||||
except pytz.exceptions.UnknownTimeZoneError:
|
||||
continue
|
||||
|
||||
# Sort by offset and then by label
|
||||
timezone_list.sort(key=lambda x: (x["offset"], x["label"]))
|
||||
|
||||
# Remove offset from final output
|
||||
for tz in timezone_list:
|
||||
del tz["offset"]
|
||||
|
||||
return Response({"timezones": timezone_list}, status=status.HTTP_200_OK)
|
||||
@@ -1 +0,0 @@
|
||||
from .api import *
|
||||
@@ -1,34 +0,0 @@
|
||||
# Third party imports
|
||||
from rest_framework.test import APITestCase, APIClient
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import User
|
||||
from plane.app.views.authentication import get_tokens_for_user
|
||||
|
||||
|
||||
class BaseAPITest(APITestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient(HTTP_USER_AGENT="plane/test", REMOTE_ADDR="10.10.10.10")
|
||||
|
||||
|
||||
class AuthenticatedAPITest(BaseAPITest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
## Create Dummy User
|
||||
self.email = "user@plane.so"
|
||||
user = User.objects.create(email=self.email)
|
||||
user.set_password("user@123")
|
||||
user.save()
|
||||
|
||||
# Set user
|
||||
self.user = user
|
||||
|
||||
# Set Up User ID
|
||||
self.user_id = user.id
|
||||
|
||||
access_token, _ = get_tokens_for_user(user)
|
||||
self.access_token = access_token
|
||||
|
||||
# Set Up Authentication Token
|
||||
self.client.credentials(HTTP_AUTHORIZATION="Bearer " + access_token)
|
||||
@@ -1 +0,0 @@
|
||||
# TODO: Tests for File Asset Uploads
|
||||
@@ -1 +0,0 @@
|
||||
# TODO: Tests for ChangePassword and other Endpoints
|
||||
@@ -1,183 +0,0 @@
|
||||
# Python import
|
||||
import json
|
||||
|
||||
# Django imports
|
||||
from django.urls import reverse
|
||||
|
||||
# Third Party imports
|
||||
from rest_framework import status
|
||||
from .base import BaseAPITest
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import User
|
||||
from plane.settings.redis import redis_instance
|
||||
|
||||
|
||||
class SignInEndpointTests(BaseAPITest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
user = User.objects.create(email="user@plane.so")
|
||||
user.set_password("user@123")
|
||||
user.save()
|
||||
|
||||
def test_without_data(self):
|
||||
url = reverse("sign-in")
|
||||
response = self.client.post(url, {}, format="json")
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def test_email_validity(self):
|
||||
url = reverse("sign-in")
|
||||
response = self.client.post(
|
||||
url, {"email": "useremail.com", "password": "user@123"}, format="json"
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(
|
||||
response.data, {"error": "Please provide a valid email address."}
|
||||
)
|
||||
|
||||
def test_password_validity(self):
|
||||
url = reverse("sign-in")
|
||||
response = self.client.post(
|
||||
url, {"email": "user@plane.so", "password": "user123"}, format="json"
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(
|
||||
response.data,
|
||||
{
|
||||
"error": "Sorry, we could not find a user with the provided credentials. Please try again."
|
||||
},
|
||||
)
|
||||
|
||||
def test_user_exists(self):
|
||||
url = reverse("sign-in")
|
||||
response = self.client.post(
|
||||
url, {"email": "user@email.so", "password": "user123"}, format="json"
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
self.assertEqual(
|
||||
response.data,
|
||||
{
|
||||
"error": "Sorry, we could not find a user with the provided credentials. Please try again."
|
||||
},
|
||||
)
|
||||
|
||||
def test_user_login(self):
|
||||
url = reverse("sign-in")
|
||||
|
||||
response = self.client.post(
|
||||
url, {"email": "user@plane.so", "password": "user@123"}, format="json"
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data.get("user").get("email"), "user@plane.so")
|
||||
|
||||
|
||||
class MagicLinkGenerateEndpointTests(BaseAPITest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
user = User.objects.create(email="user@plane.so")
|
||||
user.set_password("user@123")
|
||||
user.save()
|
||||
|
||||
def test_without_data(self):
|
||||
url = reverse("magic-generate")
|
||||
response = self.client.post(url, {}, format="json")
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def test_email_validity(self):
|
||||
url = reverse("magic-generate")
|
||||
response = self.client.post(url, {"email": "useremail.com"}, format="json")
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(
|
||||
response.data, {"error": "Please provide a valid email address."}
|
||||
)
|
||||
|
||||
def test_magic_generate(self):
|
||||
url = reverse("magic-generate")
|
||||
|
||||
ri = redis_instance()
|
||||
ri.delete("magic_user@plane.so")
|
||||
|
||||
response = self.client.post(url, {"email": "user@plane.so"}, format="json")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_max_generate_attempt(self):
|
||||
url = reverse("magic-generate")
|
||||
|
||||
ri = redis_instance()
|
||||
ri.delete("magic_user@plane.so")
|
||||
|
||||
for _ in range(4):
|
||||
response = self.client.post(url, {"email": "user@plane.so"}, format="json")
|
||||
|
||||
response = self.client.post(url, {"email": "user@plane.so"}, format="json")
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(
|
||||
response.data, {"error": "Max attempts exhausted. Please try again later."}
|
||||
)
|
||||
|
||||
|
||||
class MagicSignInEndpointTests(BaseAPITest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
user = User.objects.create(email="user@plane.so")
|
||||
user.set_password("user@123")
|
||||
user.save()
|
||||
|
||||
def test_without_data(self):
|
||||
url = reverse("magic-sign-in")
|
||||
response = self.client.post(url, {}, format="json")
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(response.data, {"error": "User token and key are required"})
|
||||
|
||||
def test_expired_invalid_magic_link(self):
|
||||
ri = redis_instance()
|
||||
ri.delete("magic_user@plane.so")
|
||||
|
||||
url = reverse("magic-sign-in")
|
||||
response = self.client.post(
|
||||
url,
|
||||
{"key": "magic_user@plane.so", "token": "xxxx-xxxxx-xxxx"},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(
|
||||
response.data, {"error": "The magic code/link has expired please try again"}
|
||||
)
|
||||
|
||||
def test_invalid_magic_code(self):
|
||||
ri = redis_instance()
|
||||
ri.delete("magic_user@plane.so")
|
||||
## Create Token
|
||||
url = reverse("magic-generate")
|
||||
self.client.post(url, {"email": "user@plane.so"}, format="json")
|
||||
|
||||
url = reverse("magic-sign-in")
|
||||
response = self.client.post(
|
||||
url,
|
||||
{"key": "magic_user@plane.so", "token": "xxxx-xxxxx-xxxx"},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(
|
||||
response.data, {"error": "Your login code was incorrect. Please try again."}
|
||||
)
|
||||
|
||||
def test_magic_code_sign_in(self):
|
||||
ri = redis_instance()
|
||||
ri.delete("magic_user@plane.so")
|
||||
## Create Token
|
||||
url = reverse("magic-generate")
|
||||
self.client.post(url, {"email": "user@plane.so"}, format="json")
|
||||
|
||||
# Get the token
|
||||
user_data = json.loads(ri.get("magic_user@plane.so"))
|
||||
token = user_data["token"]
|
||||
|
||||
url = reverse("magic-sign-in")
|
||||
response = self.client.post(
|
||||
url, {"key": "magic_user@plane.so", "token": token}, format="json"
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data.get("user").get("email"), "user@plane.so")
|
||||
@@ -1 +0,0 @@
|
||||
# TODO: Write Test for Cycle Endpoints
|
||||
@@ -1 +0,0 @@
|
||||
# TODO: Write Test for Issue Endpoints
|
||||
@@ -1 +0,0 @@
|
||||
# TODO: Tests for OAuth Authentication Endpoint
|
||||
@@ -1 +0,0 @@
|
||||
# TODO: Write Test for people Endpoint
|
||||
@@ -1 +0,0 @@
|
||||
# TODO: Write Tests for project endpoints
|
||||
@@ -1 +0,0 @@
|
||||
# TODO: Write Test for shortcuts
|
||||
@@ -1 +0,0 @@
|
||||
# TODO: Wrote test for state endpoints
|
||||
@@ -1 +0,0 @@
|
||||
# TODO: Write test for view endpoints
|
||||
@@ -1,44 +0,0 @@
|
||||
# Django imports
|
||||
from django.urls import reverse
|
||||
|
||||
# Third party import
|
||||
from rest_framework import status
|
||||
|
||||
# Module imports
|
||||
from .base import AuthenticatedAPITest
|
||||
from plane.db.models import Workspace, WorkspaceMember
|
||||
|
||||
|
||||
class WorkSpaceCreateReadUpdateDelete(AuthenticatedAPITest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
def test_create_workspace(self):
|
||||
url = reverse("workspace")
|
||||
|
||||
# Test with empty data
|
||||
response = self.client.post(url, {}, format="json")
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Test with valid data
|
||||
response = self.client.post(
|
||||
url, {"name": "Plane", "slug": "pla-ne"}, format="json"
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(Workspace.objects.count(), 1)
|
||||
# Check if the member is created
|
||||
self.assertEqual(WorkspaceMember.objects.count(), 1)
|
||||
|
||||
# Check other values
|
||||
workspace = Workspace.objects.get(pk=response.data["id"])
|
||||
workspace_member = WorkspaceMember.objects.get(
|
||||
workspace=workspace, member_id=self.user_id
|
||||
)
|
||||
self.assertEqual(workspace.owner_id, self.user_id)
|
||||
self.assertEqual(workspace_member.role, 20)
|
||||
|
||||
# Create a already existing workspace
|
||||
response = self.client.post(
|
||||
url, {"name": "Plane", "slug": "pla-ne"}, format="json"
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
|
||||
@@ -1,4 +0,0 @@
|
||||
-r base.txt
|
||||
# test checker
|
||||
pytest==7.1.2
|
||||
coverage==6.5.0
|
||||
@@ -26,16 +26,16 @@ export const InstanceAIForm: FC<IInstanceAIForm> = (props) => {
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<AIFormValues>({
|
||||
defaultValues: {
|
||||
OPENAI_API_KEY: config["OPENAI_API_KEY"],
|
||||
GPT_ENGINE: config["GPT_ENGINE"],
|
||||
LLM_API_KEY: config["LLM_API_KEY"],
|
||||
LLM_MODEL: config["LLM_MODEL"],
|
||||
},
|
||||
});
|
||||
|
||||
const aiFormFields: TControllerInputFormField[] = [
|
||||
{
|
||||
key: "GPT_ENGINE",
|
||||
key: "LLM_MODEL",
|
||||
type: "text",
|
||||
label: "GPT_ENGINE",
|
||||
label: "LLM Model",
|
||||
description: (
|
||||
<>
|
||||
Choose an OpenAI engine.{" "}
|
||||
@@ -49,12 +49,12 @@ export const InstanceAIForm: FC<IInstanceAIForm> = (props) => {
|
||||
</a>
|
||||
</>
|
||||
),
|
||||
placeholder: "gpt-3.5-turbo",
|
||||
error: Boolean(errors.GPT_ENGINE),
|
||||
placeholder: "gpt-4o-mini",
|
||||
error: Boolean(errors.LLM_MODEL),
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "OPENAI_API_KEY",
|
||||
key: "LLM_API_KEY",
|
||||
type: "password",
|
||||
label: "API key",
|
||||
description: (
|
||||
@@ -71,7 +71,7 @@ export const InstanceAIForm: FC<IInstanceAIForm> = (props) => {
|
||||
</>
|
||||
),
|
||||
placeholder: "sk-asddassdfasdefqsdfasd23das3dasdcasd",
|
||||
error: Boolean(errors.OPENAI_API_KEY),
|
||||
error: Boolean(errors.LLM_API_KEY),
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
+1
-5
@@ -98,11 +98,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
|
||||
key: "GITHUB_ORGANIZATION_ID",
|
||||
type: "text",
|
||||
label: "Organization ID",
|
||||
description: (
|
||||
<>
|
||||
The organization github ID.
|
||||
</>
|
||||
),
|
||||
description: <>The organization github ID.</>,
|
||||
placeholder: "123456789",
|
||||
error: Boolean(errors.GITHUB_ORGANIZATION_ID),
|
||||
required: false,
|
||||
+1
-1
@@ -7,7 +7,7 @@ import { LogOut, UserCog2, Palette } from "lucide-react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
// plane internal packages
|
||||
import { API_BASE_URL } from "@plane/constants";
|
||||
import {AuthService } from "@plane/services";
|
||||
import { AuthService } from "@plane/services";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { getFileURL, cn } from "@plane/utils";
|
||||
// hooks
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
import { FC } from "react";
|
||||
import { Info, X } from "lucide-react";
|
||||
// plane constants
|
||||
import { TAuthErrorInfo } from "@plane/constants";
|
||||
import { TAdminAuthErrorInfo } from "@plane/constants";
|
||||
|
||||
type TAuthBanner = {
|
||||
bannerData: TAuthErrorInfo | undefined;
|
||||
handleBannerData?: (bannerData: TAuthErrorInfo | undefined) => void;
|
||||
bannerData: TAdminAuthErrorInfo | undefined;
|
||||
handleBannerData?: (bannerData: TAdminAuthErrorInfo | undefined) => void;
|
||||
};
|
||||
|
||||
export const AuthBanner: FC<TAuthBanner> = (props) => {
|
||||
+2
-2
@@ -4,7 +4,7 @@ import { FC, useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
// plane internal packages
|
||||
import { API_BASE_URL, EAdminAuthErrorCodes, TAuthErrorInfo } from "@plane/constants";
|
||||
import { API_BASE_URL, EAdminAuthErrorCodes, TAdminAuthErrorInfo } from "@plane/constants";
|
||||
import { AuthService } from "@plane/services";
|
||||
import { Button, Input, Spinner } from "@plane/ui";
|
||||
// components
|
||||
@@ -54,7 +54,7 @@ export const InstanceSignInForm: FC = (props) => {
|
||||
const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined);
|
||||
const [formData, setFormData] = useState<TFormData>(defaultFromData);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
|
||||
const [errorInfo, setErrorInfo] = useState<TAdminAuthErrorInfo | undefined>(undefined);
|
||||
|
||||
const handleFormChange = (key: keyof TFormData, value: string | boolean) =>
|
||||
setFormData((prev) => ({ ...prev, [key]: value }));
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user