master
BottomOfCulture 2 years ago
commit 1e6ec47c5e

12
.gitignore vendored

@ -0,0 +1,12 @@
.idea
__pycache__
db.sqlite3
*.py[oc]
frontend/.parcel-cache
frontend/build
frontend/dist
frontend/node_modules
frontend/yarn-error.log
public/static/*
public/uploads/*
!.gitkeep

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -0,0 +1,6 @@
from django.apps import AppConfig
class AppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'app'

@ -0,0 +1,27 @@
# Generated by Django 4.0.5 on 2022-06-05 21:56
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Task',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('description', models.TextField()),
('is_done', models.BooleanField(default=False)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

@ -0,0 +1,12 @@
from django.contrib.auth import get_user_model
from django.db import models
class Task(models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
is_done = models.BooleanField(default=False)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
def __str__(self):
return self.title

@ -0,0 +1,21 @@
from typing import Optional, List
import strawberry
from graphql import ExecutionContext, GraphQLError
from articles.utils import AuthExtension, GraphQLError as CustomGraphQLError
from .mutations import Mutation
from .queries import Query
class CustomSchema(strawberry.Schema):
def process_errors(self,
errors: List[GraphQLError],
execution_context: Optional[ExecutionContext] = None):
super().process_errors(
[e for e in errors if not isinstance(e.original_error, CustomGraphQLError)],
execution_context
)
schema = CustomSchema(query=Query, mutation=Mutation, extensions=[AuthExtension])

@ -0,0 +1,58 @@
import jwt
import strawberry
from django.conf import settings
from django.contrib.auth.models import User
from strawberry import ID
from strawberry.types import Info
from app.models import Task
from app.schema.types import TaskType
from articles.utils import GraphQLError, IsAuthenticated
@strawberry.type
class Mutation:
@strawberry.mutation(permission_classes=[IsAuthenticated])
def add_task(self, title: str, description: str, info: Info) -> TaskType:
task = Task.objects.create(title=title, description=description, user=info.context.user)
return task
@strawberry.mutation(permission_classes=[IsAuthenticated])
def change_done(self, task_id: ID) -> TaskType:
try:
task = Task.objects.get(id=task_id)
task.is_done = not task.is_done
task.save()
return task
except Task.DoesNotExist:
raise GraphQLError('Task not found')
@strawberry.mutation(permission_classes=[IsAuthenticated])
def delete_task(self, task_id: ID) -> bool:
try:
task = Task.objects.get(id=task_id)
task.delete()
return True
except Task.DoesNotExist:
raise GraphQLError('Task not found')
@strawberry.mutation
def sign_up(self, username: str, password: str) -> bool:
if User.objects.filter(username=username).exists():
raise GraphQLError('Username is unavailable')
user = User(username=username)
user.set_password(password)
user.save()
return True
@strawberry.mutation
def sign_in(self, username: str, password: str) -> str:
try:
user = User.objects.get(username=username)
if user.check_password(password):
token = jwt.encode({'id': user.pk}, settings.SECRET_KEY, algorithm='HS256')
return token
else:
raise User.DoesNotExist
except User.DoesNotExist:
raise GraphQLError('Username and/or password are incorrect')

@ -0,0 +1,13 @@
from typing import List
import strawberry
from app.models import Task
from app.schema.types import TaskType
@strawberry.type
class Query:
@strawberry.field
def get_tasks(self) -> List[TaskType]:
return Task.objects.all()

@ -0,0 +1,11 @@
import strawberry.django
from strawberry import auto
from app.models import Task
@strawberry.django.type(model=Task)
class TaskType:
title: auto
description: auto
is_done: auto
id: auto

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

@ -0,0 +1,16 @@
"""
ASGI config for articles project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'articles.settings')
application = get_asgi_application()

@ -0,0 +1,145 @@
"""
Django settings for articles project.
Generated by 'django-admin startproject' using Django 4.0.5.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""
import logging, environ
from pathlib import Path
env = environ.Env()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
env_file = BASE_DIR / '.env'
if env_file.exists():
env.read_env(str(env_file))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-(v9f%n2$y#k^&hu#@bh6i4_5b21uqrx5y67^sxis$_p=4xtas1'
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = 'HTTP_X_FORWARDED_PROTO', 'https'
# SECURITY WARNING: don't run with debug turned on in production!
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=['*'])
DEBUG = env.bool('DJANGO_DEBUG', True)
# Application definition
INSTALLED_APPS = [
'app',
'whitenoise.runserver_nostatic',
'corsheaders',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'spa.middleware.SPAMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
CORS_ALLOWED_ORIGINS = [
'http://localhost:31235'
]
ROOT_URLCONF = 'articles.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'articles.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
DATABASES = {
'default': env.db('DATABASE_URL', default='sqlite:///db.sqlite3')
}
STATICFILES_STORAGE = 'spa.storage.SPAStaticFilesStorage'
# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
STATICFILES_DIRS = [BASE_DIR/'frontend/build', ]
STATIC_ROOT = BASE_DIR/"public/static"

@ -0,0 +1,11 @@
from django.contrib import admin
from django.urls import path
from strawberry.django.views import GraphQLView
from app.schema import schema
urlpatterns = [
path('admin/', admin.site.urls),
path('graphql', GraphQLView.as_view(schema=schema))
]

@ -0,0 +1,32 @@
from typing import Any
import jwt
from django.conf import settings
from django.contrib.auth.models import User, AnonymousUser
from django.http import HttpRequest
from strawberry import BasePermission
from strawberry.extensions import Extension
from strawberry.types import Info
class GraphQLError(Exception):
pass
class AuthExtension(Extension):
def on_request_start(self):
request = self.execution_context.context.request
auth = request.headers.get('authorization')
if auth:
try:
payload = jwt.decode(auth, settings.SECRET_KEY, algorithms=['HS256'])
self.execution_context.context.user = User.objects.get(pk=payload['id'])
return
except (jwt.DecodeError, User.DoesNotExist):
pass
self.execution_context.context.user = AnonymousUser()
class IsAuthenticated(BasePermission):
def has_permission(self, source: Any, info: Info, **kwargs) -> bool:
return info.context.user.is_authenticated

@ -0,0 +1,17 @@
"""
WSGI config for articles project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'articles.settings')
application = get_wsgi_application()

@ -0,0 +1,12 @@
overwrite: true
schema: "./src/graphql/schema.graphql"
documents: "./src/graphql/*.graphql"
generates:
./src/graphql/tscode.tsx:
plugins:
- "typescript"
- "typescript-operations"
- "typescript-react-apollo"
./graphql.schema.json:
plugins:
- "introspection"

File diff suppressed because it is too large Load Diff

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
<title>YourTasks</title>
</head>
<body>
<div id="root"></div>
<script src="./src/index.tsx" type="module"></script>
</body>
</html>

@ -0,0 +1,41 @@
{
"devDependencies": {
"@graphql-codegen/cli": "2.6.2",
"@graphql-codegen/introspection": "2.1.1",
"@graphql-codegen/typescript": "2.4.11",
"@graphql-codegen/typescript-operations": "2.4.0",
"@graphql-codegen/typescript-react-apollo": "3.2.14",
"@types/node": "^17.0.42",
"@types/react": "^18.0.12",
"@types/react-dom": "^18.0.5",
"parcel": "^2.6.0",
"process": "^0.11.10",
"typescript": "^4.7.3"
},
"dependencies": {
"@apollo/client": "^3.6.6",
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@mui/icons-material": "^5.8.3",
"@mui/lab": "^5.0.0-alpha.84",
"@mui/material": "^5.8.2",
"@types/store": "^2.0.2",
"graphql": "^16.5.0",
"inversify": "^6.0.1",
"inversify-react": "^1.0.2",
"mobx": "^6.6.0",
"mobx-react": "^7.5.0",
"mobx-react-lite": "^3.4.0",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-hook-form": "^7.31.3",
"react-router-dom": "^6.3.0",
"reflect-metadata": "^0.1.13",
"store": "^2.0.12"
},
"scripts": {
"start": "parcel index.html -p 31235",
"gqlcg": "graphql-codegen --config codegen.yml",
"build": "parcel build index.html --dist-dir build"
}
}

@ -0,0 +1,66 @@
import React from 'react';
import { createTheme, CssBaseline, ThemeProvider } from "@mui/material";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import SignInPage from "./pages/SignInPage";
import { ApolloClient, ApolloProvider, createHttpLink, InMemoryCache } from "@apollo/client";
import SignUpPage from "./pages/SignUpPage";
import { setContext } from "@apollo/client/link/context";
import TasksPage from "./pages/TasksPage";
import TokenCheck from "./components/TokenCheck";
import { RootStore } from "./stores/RootStore";
import { Provider } from "inversify-react";
import Layout from "./components/Layout";
type AppProps = {}
const darkTheme = createTheme({
palette: {
mode: 'dark',
},
})
const store = new RootStore()
const httpLink = createHttpLink({
uri: process.env.NODE_ENV == 'development' ? 'http://localhost:31234/graphql' : '/graphql',
})
const authLink = setContext((_, { headers }) => {
const token = store.authStore.token
return {
headers: {
...headers,
authorization: token ? `${token}` : ''
}
}
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
})
const App = ({}: AppProps) => {
return (
<React.StrictMode>
<ApolloProvider client={client}>
<Provider container={store.container}>
<BrowserRouter>
<ThemeProvider theme={darkTheme}>
<CssBaseline/>
<Routes>
<Route path='/' element={<Layout />}>
<Route path='signin' element={<TokenCheck required={false}><SignInPage/></TokenCheck>}/>
<Route path='signup' element={<TokenCheck required={false}><SignUpPage/></TokenCheck>}/>
<Route index element={<TokenCheck><TasksPage/></TokenCheck>}/>
</Route>
</Routes>
</ThemeProvider>
</BrowserRouter>
</Provider>
</ApolloProvider>
</React.StrictMode>
)
};
export default App;

@ -0,0 +1,40 @@
import React from 'react';
import { AppBar, Button, Container, Stack, Toolbar, Typography, useTheme } from "@mui/material";
import { Link, Outlet, useNavigate } from "react-router-dom";
import { useInjection } from "inversify-react";
import { AuthStore } from "../stores/AuthStore";
import { observer } from "mobx-react";
import { UIStore } from "../stores/UIStore";
type AppBarProps = {}
const Layout = observer(({}: AppBarProps) => {
const navigate = useNavigate();
const authStore = useInjection(AuthStore);
const uiStore = useInjection(UIStore)
return (
<Container sx={{ pt: 7.8, height: '100vh' }}>
<AppBar>
<Container>
<Toolbar>
<Typography variant='h5' sx={{ flexGrow: 1 }}>{uiStore.title}</Typography>
{authStore.token ?
<Button variant='outlined' color='error' onClick={() => {
navigate('/signin');
authStore.setToken('');
}}>Sign Out</Button> :
<Stack spacing={1} direction='row'>
<Button component={Link} variant='outlined' to='/signin' >Sign In</Button>
<Button component={Link} variant='outlined' to='/signup' >Sign Up</Button>
</Stack>
}
</Toolbar>
</Container>
</AppBar>
<Outlet />
</Container>
)
});
export default Layout;

@ -0,0 +1,16 @@
import React, { useEffect } from 'react';
import { useInjection } from "inversify-react";
import { UIStore } from "../stores/UIStore";
type SetTitleProps = {
children: string
}
const SetTitle = ({ children }: SetTitleProps) => {
const uiStore = useInjection(UIStore)
useEffect(() => uiStore.setTitle(children), [])
return null
};
export default SetTitle;

@ -0,0 +1,31 @@
import React, { useEffect } from 'react';
import { observer } from "mobx-react";
import { Navigate } from "react-router-dom";
import { useInjection } from "inversify-react";
import { AuthStore } from "../stores/AuthStore";
type TokenCheckProps = {
children: React.ReactNode | React.ReactNode[];
required?: boolean;
}
const TokenCheck = observer(({ children, required = true }: TokenCheckProps) => {
const authStore = useInjection(AuthStore);
useEffect(() => {
console.log('token changed', authStore.token);
}, [authStore.token]);
if (required && !authStore.token)
return <Navigate to='/signin' state='not_signed_in' />;
else if (!required && authStore.token)
return <Navigate to='/' />;
return (
<>
{children}
</>
)
});
export default TokenCheck;

@ -0,0 +1,15 @@
{
"name": "Untitled GraphQL Schema",
"schemaPath": "schema.graphql",
"extensions": {
"endpoints": {
"Default GraphQL Endpoint": {
"url": "http://localhost:31234/graphql",
"headers": {
"user-agent": "JS GraphQL"
},
"introspect": false
}
}
}
}

@ -0,0 +1,6 @@
fragment task on TaskType {
description
isDone
title
id
}

@ -0,0 +1,23 @@
mutation addTask($description: String!, $title: String!) {
addTask(description: $description, title: $title) {
...task
}
}
mutation changeDone($id: ID!) {
changeDone(taskId: $id) {
...task
}
}
mutation deleteTask($id: ID!) {
deleteTask(taskId: $id)
}
mutation signIn($password: String!, $username: String!) {
signIn(password: $password, username: $username)
}
mutation signUp($password: String!, $username: String!) {
signUp(password: $password, username: $username)
}

@ -0,0 +1,5 @@
query getTasks {
getTasks {
...task
}
}

@ -0,0 +1,25 @@
# This file was generated based on ".graphqlconfig". Do not edit manually.
schema {
query: Query
mutation: Mutation
}
type Mutation {
addTask(description: String!, title: String!): TaskType!
changeDone(taskId: ID!): TaskType!
deleteTask(taskId: ID!): Boolean!
signIn(password: String!, username: String!): String!
signUp(password: String!, username: String!): Boolean!
}
type Query {
getTasks: [TaskType!]!
}
type TaskType {
description: String!
id: ID!
isDone: Boolean!
title: String!
}

@ -0,0 +1,316 @@
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
const defaultOptions = {} as const;
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
};
export type Mutation = {
__typename?: 'Mutation';
addTask: TaskType;
changeDone: TaskType;
deleteTask: Scalars['Boolean'];
signIn: Scalars['String'];
signUp: Scalars['Boolean'];
};
export type MutationAddTaskArgs = {
description: Scalars['String'];
title: Scalars['String'];
};
export type MutationChangeDoneArgs = {
taskId: Scalars['ID'];
};
export type MutationDeleteTaskArgs = {
taskId: Scalars['ID'];
};
export type MutationSignInArgs = {
password: Scalars['String'];
username: Scalars['String'];
};
export type MutationSignUpArgs = {
password: Scalars['String'];
username: Scalars['String'];
};
export type Query = {
__typename?: 'Query';
getTasks: Array<TaskType>;
};
export type TaskType = {
__typename?: 'TaskType';
description: Scalars['String'];
id: Scalars['ID'];
isDone: Scalars['Boolean'];
title: Scalars['String'];
};
export type TaskFragment = { __typename?: 'TaskType', description: string, isDone: boolean, title: string, id: string };
export type AddTaskMutationVariables = Exact<{
description: Scalars['String'];
title: Scalars['String'];
}>;
export type AddTaskMutation = { __typename?: 'Mutation', addTask: { __typename?: 'TaskType', description: string, isDone: boolean, title: string, id: string } };
export type ChangeDoneMutationVariables = Exact<{
id: Scalars['ID'];
}>;
export type ChangeDoneMutation = { __typename?: 'Mutation', changeDone: { __typename?: 'TaskType', description: string, isDone: boolean, title: string, id: string } };
export type DeleteTaskMutationVariables = Exact<{
id: Scalars['ID'];
}>;
export type DeleteTaskMutation = { __typename?: 'Mutation', deleteTask: boolean };
export type SignInMutationVariables = Exact<{
password: Scalars['String'];
username: Scalars['String'];
}>;
export type SignInMutation = { __typename?: 'Mutation', signIn: string };
export type SignUpMutationVariables = Exact<{
password: Scalars['String'];
username: Scalars['String'];
}>;
export type SignUpMutation = { __typename?: 'Mutation', signUp: boolean };
export type GetTasksQueryVariables = Exact<{ [key: string]: never; }>;
export type GetTasksQuery = { __typename?: 'Query', getTasks: Array<{ __typename?: 'TaskType', description: string, isDone: boolean, title: string, id: string }> };
export const TaskFragmentDoc = gql`
fragment task on TaskType {
description
isDone
title
id
}
`;
export const AddTaskDocument = gql`
mutation addTask($description: String!, $title: String!) {
addTask(description: $description, title: $title) {
...task
}
}
${TaskFragmentDoc}`;
export type AddTaskMutationFn = Apollo.MutationFunction<AddTaskMutation, AddTaskMutationVariables>;
/**
* __useAddTaskMutation__
*
* To run a mutation, you first call `useAddTaskMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useAddTaskMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [addTaskMutation, { data, loading, error }] = useAddTaskMutation({
* variables: {
* description: // value for 'description'
* title: // value for 'title'
* },
* });
*/
export function useAddTaskMutation(baseOptions?: Apollo.MutationHookOptions<AddTaskMutation, AddTaskMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<AddTaskMutation, AddTaskMutationVariables>(AddTaskDocument, options);
}
export type AddTaskMutationHookResult = ReturnType<typeof useAddTaskMutation>;
export type AddTaskMutationResult = Apollo.MutationResult<AddTaskMutation>;
export type AddTaskMutationOptions = Apollo.BaseMutationOptions<AddTaskMutation, AddTaskMutationVariables>;
export const ChangeDoneDocument = gql`
mutation changeDone($id: ID!) {
changeDone(taskId: $id) {
...task
}
}
${TaskFragmentDoc}`;
export type ChangeDoneMutationFn = Apollo.MutationFunction<ChangeDoneMutation, ChangeDoneMutationVariables>;
/**
* __useChangeDoneMutation__
*
* To run a mutation, you first call `useChangeDoneMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useChangeDoneMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [changeDoneMutation, { data, loading, error }] = useChangeDoneMutation({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useChangeDoneMutation(baseOptions?: Apollo.MutationHookOptions<ChangeDoneMutation, ChangeDoneMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<ChangeDoneMutation, ChangeDoneMutationVariables>(ChangeDoneDocument, options);
}
export type ChangeDoneMutationHookResult = ReturnType<typeof useChangeDoneMutation>;
export type ChangeDoneMutationResult = Apollo.MutationResult<ChangeDoneMutation>;
export type ChangeDoneMutationOptions = Apollo.BaseMutationOptions<ChangeDoneMutation, ChangeDoneMutationVariables>;
export const DeleteTaskDocument = gql`
mutation deleteTask($id: ID!) {
deleteTask(taskId: $id)
}
`;
export type DeleteTaskMutationFn = Apollo.MutationFunction<DeleteTaskMutation, DeleteTaskMutationVariables>;
/**
* __useDeleteTaskMutation__
*
* To run a mutation, you first call `useDeleteTaskMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteTaskMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [deleteTaskMutation, { data, loading, error }] = useDeleteTaskMutation({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useDeleteTaskMutation(baseOptions?: Apollo.MutationHookOptions<DeleteTaskMutation, DeleteTaskMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<DeleteTaskMutation, DeleteTaskMutationVariables>(DeleteTaskDocument, options);
}
export type DeleteTaskMutationHookResult = ReturnType<typeof useDeleteTaskMutation>;
export type DeleteTaskMutationResult = Apollo.MutationResult<DeleteTaskMutation>;
export type DeleteTaskMutationOptions = Apollo.BaseMutationOptions<DeleteTaskMutation, DeleteTaskMutationVariables>;
export const SignInDocument = gql`
mutation signIn($password: String!, $username: String!) {
signIn(password: $password, username: $username)
}
`;
export type SignInMutationFn = Apollo.MutationFunction<SignInMutation, SignInMutationVariables>;
/**
* __useSignInMutation__
*
* To run a mutation, you first call `useSignInMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useSignInMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [signInMutation, { data, loading, error }] = useSignInMutation({
* variables: {
* password: // value for 'password'
* username: // value for 'username'
* },
* });
*/
export function useSignInMutation(baseOptions?: Apollo.MutationHookOptions<SignInMutation, SignInMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<SignInMutation, SignInMutationVariables>(SignInDocument, options);
}
export type SignInMutationHookResult = ReturnType<typeof useSignInMutation>;
export type SignInMutationResult = Apollo.MutationResult<SignInMutation>;
export type SignInMutationOptions = Apollo.BaseMutationOptions<SignInMutation, SignInMutationVariables>;
export const SignUpDocument = gql`
mutation signUp($password: String!, $username: String!) {
signUp(password: $password, username: $username)
}
`;
export type SignUpMutationFn = Apollo.MutationFunction<SignUpMutation, SignUpMutationVariables>;
/**
* __useSignUpMutation__
*
* To run a mutation, you first call `useSignUpMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useSignUpMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [signUpMutation, { data, loading, error }] = useSignUpMutation({
* variables: {
* password: // value for 'password'
* username: // value for 'username'
* },
* });
*/
export function useSignUpMutation(baseOptions?: Apollo.MutationHookOptions<SignUpMutation, SignUpMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<SignUpMutation, SignUpMutationVariables>(SignUpDocument, options);
}
export type SignUpMutationHookResult = ReturnType<typeof useSignUpMutation>;
export type SignUpMutationResult = Apollo.MutationResult<SignUpMutation>;
export type SignUpMutationOptions = Apollo.BaseMutationOptions<SignUpMutation, SignUpMutationVariables>;
export const GetTasksDocument = gql`
query getTasks {
getTasks {
...task
}
}
${TaskFragmentDoc}`;
/**
* __useGetTasksQuery__
*
* To run a query within a React component, call `useGetTasksQuery` and pass it any options that fit your needs.
* When your component renders, `useGetTasksQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetTasksQuery({
* variables: {
* },
* });
*/
export function useGetTasksQuery(baseOptions?: Apollo.QueryHookOptions<GetTasksQuery, GetTasksQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetTasksQuery, GetTasksQueryVariables>(GetTasksDocument, options);
}
export function useGetTasksLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetTasksQuery, GetTasksQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetTasksQuery, GetTasksQueryVariables>(GetTasksDocument, options);
}
export type GetTasksQueryHookResult = ReturnType<typeof useGetTasksQuery>;
export type GetTasksLazyQueryHookResult = ReturnType<typeof useGetTasksLazyQuery>;
export type GetTasksQueryResult = Apollo.QueryResult<GetTasksQuery, GetTasksQueryVariables>;

@ -0,0 +1,18 @@
import { createRoot } from "react-dom/client";
import "reflect-metadata"
import App from "./App";
const root = createRoot(
document.querySelector('#root')!
)
const element = (
<App/>
);
root.render(element)
// @ts-ignore
if (Boolean(module.hot)) {
// @ts-ignore
module.hot.accept()
}

@ -0,0 +1,103 @@
import React, { useEffect, useState } from 'react';
import { Alert, Box, Button, Collapse, Paper, TextField, Toolbar, Typography } from "@mui/material";
import { LoadingButton } from "@mui/lab";
import { useForm } from "react-hook-form";
import { useSignInMutation } from "../graphql/tscode";
import { Link, useLocation } from "react-router-dom";
import { observer } from "mobx-react";
import { useInjection } from "inversify-react";
import { AuthStore } from "../stores/AuthStore";
import SetTitle from "../components/SetTitle";
type LoginPageProps = {}
type Inputs = {
username: string,
password: string,
}
const SignInPage = observer(({}: LoginPageProps) => {
const authStore = useInjection(AuthStore);
const location = useLocation();
const { register, handleSubmit, formState: { errors } } = useForm<Inputs>()
const [ signUpSuccess, setSignUpSuccess ] = useState(false);
const [ notSignedIn, setNotSignedIn ] = useState(false);
useEffect(() => {
switch (location.state) {
case 'not_signed_in':
setNotSignedIn(true)
setTimeout(() => {
setNotSignedIn(false)
}, 5000)
break
case 'signup_success':
setSignUpSuccess(true)
setTimeout(() => {
setSignUpSuccess(false)
}, 5000)
}
}, [])
const [ signInFunction, {
data,
loading,
error
} ] = useSignInMutation({ onCompleted: (result) => authStore.setToken(result.signIn) })
const onSubmit = async (data: Inputs) => {
try {
await signInFunction({ variables: data })
} catch (e) {
console.log(e)
}
}
return (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}>
<SetTitle>Signing In</SetTitle>
<Paper
onSubmit={handleSubmit(onSubmit)}
component='form'
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: 2,
width: {
xs: '90vw',
md: 400,
}
}}
>
<TextField
label='Username'
variant='standard'
error={!!errors.username}
helperText={errors.username?.message}
{...register('username', { required: 'Username is required' })}
/>
<TextField
label='Password'
type='password'
variant='standard'
margin='normal'
sx={{ mb: 3 }}
error={!!errors.password}
helperText={errors.password?.message}
{...register('password', { required: 'Password is required' })}
/>
{error && <Alert sx={{ mb: 2, maxWidth: '199px' }} variant='filled' severity='error'>{error?.message}</Alert>}
<Collapse in={signUpSuccess}><Alert sx={{ mb: 2, maxWidth: '300px' }} variant='filled' severity='success'>You have signed up!</Alert></Collapse>
<Collapse in={notSignedIn}><Alert sx={{ mb: 2, maxWidth: '300 px' }} variant='filled' severity='error'>You should sign in motherfucker!</Alert></Collapse>
<LoadingButton loading={loading} variant='outlined' type='submit' sx={{ mb: 1 }}>Sign In</LoadingButton>
<div>{authStore.token}</div>
</Paper>
</Box>
)
});
export default SignInPage;

@ -0,0 +1,78 @@
import React from 'react';
import { Alert, Box, Button, Paper, TextField, Typography } from "@mui/material";
import { LoadingButton } from "@mui/lab";
import { useForm } from "react-hook-form";
import { useSignInMutation, useSignUpMutation } from "../graphql/tscode";
import { Link, useNavigate } from "react-router-dom";
import SetTitle from "../components/SetTitle";
type SignUpPageProps = {}
type Inputs = {
username: string,
password: string,
}
const SignUpPage = ({}: SignUpPageProps) => {
const navigate = useNavigate();
const { register, handleSubmit, formState: { errors } } = useForm<Inputs>();
const [ signUpFunction, {
data,
loading,
error
} ] = useSignUpMutation({ onCompleted: (result) => {
navigate('/signin', { state: 'signup_success' });
} });
const onSubmit = async (data: Inputs) => {
try {
await signUpFunction({ variables: data });
} catch (e) {
console.log(e);
}
}
return (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}>
<SetTitle>Signing Up</SetTitle>
<Paper
onSubmit={handleSubmit(onSubmit)}
component='form'
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: 2,
width: {
xs: '90vw',
md: 400,
}
}}
>
<TextField
label='Username'
variant='standard'
error={!!errors.username}
helperText={errors.username?.message}
{...register('username', { required: 'Username is required' })}
/>
<TextField
label='Password'
type='password'
variant='standard'
margin='normal'
sx={{ mb: 3 }}
error={!!errors.password}
helperText={errors.password?.message}
{...register('password', { required: 'Password is required' })}
/>
{error && <Alert sx={{ mb: 2, maxWidth: '199px' }} variant='filled' severity='error'>{error?.message}</Alert>}
<LoadingButton loading={loading} variant='outlined' type='submit' sx={{mb: 1}}>Sign Up</LoadingButton>
</Paper>
</Box>
)
};
export default SignUpPage;

@ -0,0 +1,104 @@
import React, { useState } from 'react';
import {
Box,
Checkbox, CircularProgress,
Divider, Grid, IconButton,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
Paper, Stack,
TextField,
Typography
} from "@mui/material";
import { useForm } from "react-hook-form";
import { useAddTaskMutation, useChangeDoneMutation, useDeleteTaskMutation, useGetTasksQuery } from "../graphql/tscode";
import { LoadingButton } from "@mui/lab";
import { Delete } from "@mui/icons-material";
import SetTitle from "../components/SetTitle";
type TasksPageProps = {}
type Inputs = {
title: string,
description: string,
}
const TasksPage = ({}: TasksPageProps) => {
const { reset: resetForm, register, handleSubmit, formState: { errors } } = useForm<Inputs>()
const { loading: tasksLoading, error: tasksError, data: tasksData, refetch } = useGetTasksQuery({ onCompleted: (result) => console.log(result) })
const [ addTaskFunction, { data: addTaskData, loading: addTaskLoading, error: addTaskError } ] = useAddTaskMutation({ onCompleted: result => {console.log(result); refetch()} })
const [ changeDoneFunction, { data: changeDoneData, loading: changeDoneLoading, error: changeDoneError } ] = useChangeDoneMutation({ onCompleted: result => refetch() })
const [ deleteTaskFunction, { data: deleteTaskData, loading: deleteTaskLoading, error: deleteTaskError } ] = useDeleteTaskMutation({ onCompleted: result => refetch() })
const [ idChange, setIdChange ] = useState<string>();
const [ idDelete, setIdDelete ] = useState<string>();
const onSubmit = async (data: Inputs) => {
try {
await addTaskFunction({ variables: data })
resetForm()
} catch (e) {
console.log(e)
}
}
return (
<Box sx={{ display: 'flex', flexDirection: 'column', margin: '6px', padding: '6px' }}>
<SetTitle>Your Tasks</SetTitle>
<Grid container spacing={1}>
<Grid item xs={12} md={4}>
<Paper component='form' onSubmit={handleSubmit(onSubmit)} elevation={2} sx={{ padding: 2, display: 'flex', flexDirection: 'column' }}>
<TextField variant='standard' label='Task Title' {...register('title', { required: 'Title is required' })} sx={{ mb: 1 }}/>
<TextField variant='standard' label='Task Description' {...register('description')} sx={{ mb: 1 }}/>
<LoadingButton loading={addTaskLoading} variant='outlined' type='submit'>Add Task</LoadingButton>
</Paper>
</Grid>
<Grid item xs={12} md={8}>
<Paper>
{tasksLoading && typeof tasksData == 'undefined' ?
<Stack alignItems="center">
<CircularProgress sx={{ m: 2 }} size={600} />
</Stack> :
!tasksLoading && tasksData!.getTasks.length == 0 ?
<Typography variant='h6' sx={{ textAlign: 'center', py: 3 }}>There's no Tasks</Typography> :
<List>
{tasksData!.getTasks.map(task => (
<ListItem
key={task.id}
secondaryAction={
deleteTaskLoading && idDelete == task.id ?
<CircularProgress size={25} /> :
<IconButton edge='end' onClick={() => deleteTaskFunction({ variables: { id: task.id } })}>
<Delete color='error' />
</IconButton>
}
>
<ListItemIcon sx={{ display: 'flex', justifyContent: 'center' }}>
{changeDoneLoading && idChange == task.id ?
<CircularProgress size={25}/> :
<Checkbox onClick={() => {
setIdChange(task.id);
changeDoneFunction({ variables: { id: task.id } })
}} checked={task.isDone}/>
}
</ListItemIcon>
<ListItemText primary={task.title} secondary={task.description}/>
</ListItem>
))}
</List>
}
</Paper>
</Grid>
</Grid>
</Box>
)
};
export default TasksPage;

@ -0,0 +1,17 @@
import { action, makeObservable, observable } from "mobx";
import store from "store";
import { RootStore } from "./RootStore";
export class AuthStore {
@observable token: string = '';
constructor(private readonly rootStore: RootStore) {
makeObservable(this);
this.token = store.get('token', '')
}
@action setToken(token: string) {
this.token = token;
store.set('token', this.token)
}
}

@ -0,0 +1,14 @@
import { Container } from "inversify";
import { AuthStore } from "./AuthStore";
import { UIStore } from "./UIStore";
export class RootStore {
container: Container = new Container();
authStore: AuthStore = new AuthStore(this);
uiStore: UIStore = new UIStore(this);
constructor() {
this.container.bind(AuthStore).toConstantValue(this.authStore);
this.container.bind(UIStore).toConstantValue(this.uiStore);
}
}

@ -0,0 +1,14 @@
import { action, makeObservable, observable } from "mobx";
import { RootStore } from "./RootStore";
export class UIStore {
@observable title: string = '';
constructor(private readonly rootStore: RootStore) {
makeObservable(this);
}
@action setTitle(title: string) {
this.title = title;
}
}

@ -0,0 +1,22 @@
{
"compilerOptions": {
"strict": true,
"module": "esnext",
"esModuleInterop": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"sourceMap": true,
"jsx": "react-jsxdev",
"resolveJsonModule": true,
"useDefineForClassFields": true,
"moduleResolution": "node",
"typeRoots": ["./src"],
"strictNullChecks": true,
"types": [
"node"
]
},
"exclude": ["node_modules", "build"]
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'articles.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
Loading…
Cancel
Save