Merge branch 'graphql'
This commit is contained in:
commit
711124065d
54 changed files with 4995 additions and 402 deletions
|
@ -0,0 +1,12 @@
|
|||
require "bundler/setup"
|
||||
require "fileutils"
|
||||
|
||||
namespace :schema do
|
||||
desc "Generate share/twenty-backend/schema.graphql"
|
||||
task :regen do
|
||||
require "twenty-backend"
|
||||
schema = File.join(__dir__, "share", "twenty-backend", "schema.graphql")
|
||||
FileUtils.mkdir_p File.dirname(schema)
|
||||
File.binwrite schema, Twenty::GraphQL::Schema.to_definition
|
||||
end
|
||||
end
|
|
@ -4,6 +4,7 @@ module Twenty
|
|||
require "fileutils"
|
||||
require "webrick"
|
||||
require "active_record"
|
||||
require_relative "twenty-backend/graphql"
|
||||
require_relative "twenty-backend/servlet"
|
||||
require_relative "twenty-backend/migration"
|
||||
require_relative "twenty-backend/model"
|
||||
|
|
7
twenty-backend/lib/twenty-backend/graphql.rb
Normal file
7
twenty-backend/lib/twenty-backend/graphql.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
module Twenty::GraphQL
|
||||
require "graphql"
|
||||
require_relative "graphql/input"
|
||||
require_relative "graphql/type"
|
||||
require_relative "graphql/mutation"
|
||||
require_relative "graphql/schema"
|
||||
end
|
4
twenty-backend/lib/twenty-backend/graphql/input.rb
Normal file
4
twenty-backend/lib/twenty-backend/graphql/input.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
module Twenty::GraphQL::Input
|
||||
include GraphQL::Types
|
||||
require_relative "input/task_input"
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
module Twenty::GraphQL::Input
|
||||
class TaskInput < GraphQL::Schema::InputObject
|
||||
argument :title, String
|
||||
argument :content, String
|
||||
argument :project_id, Int
|
||||
end
|
||||
end
|
5
twenty-backend/lib/twenty-backend/graphql/mutation.rb
Normal file
5
twenty-backend/lib/twenty-backend/graphql/mutation.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
module Twenty::GraphQL
|
||||
module Mutation
|
||||
require_relative "mutation/destroy_task"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
module Twenty::GraphQL::Mutation
|
||||
class CompleteTask < GraphQL::Schema::Mutation
|
||||
argument :task_id, Int
|
||||
field :ok, Boolean
|
||||
field :errors, [String]
|
||||
|
||||
def resolve(task_id:)
|
||||
task = Twenty::Task.find(task_id)
|
||||
task.update!(status: :complete)
|
||||
{ok: true, errors: []}
|
||||
rescue => ex
|
||||
{ok: false, errors: [ex.message]}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
module Twenty::GraphQL::Mutation
|
||||
class CreateTask < GraphQL::Schema::Mutation
|
||||
field :errors, [String], null: false
|
||||
argument :input, Twenty::GraphQL::Input::TaskInput
|
||||
|
||||
def resolve(input:)
|
||||
Twenty::Task.new(input.to_h).save!
|
||||
{"errors" => []}
|
||||
rescue => ex
|
||||
{"errors" => [ex.message]}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
module Twenty::GraphQL::Mutation
|
||||
class DestroyTask < GraphQL::Schema::Mutation
|
||||
argument :task_id, Int
|
||||
field :ok, Boolean
|
||||
field :errors, [String]
|
||||
|
||||
def resolve(task_id:)
|
||||
task = Twenty::Task.find(task_id)
|
||||
task.destroy!
|
||||
{ok: true, errors: []}
|
||||
rescue => ex
|
||||
{ok: false, errors: [ex.message]}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
module Twenty::GraphQL::Mutation
|
||||
class UpdateTask < GraphQL::Schema::Mutation
|
||||
field :errors, [String], null: false
|
||||
argument :task_id, Int
|
||||
argument :input, Twenty::GraphQL::Input::TaskInput
|
||||
|
||||
def resolve(task_id:, input:)
|
||||
task = Twenty::Task.find_by(id: task_id)
|
||||
task.update!(input.to_h)
|
||||
{"errors" => []}
|
||||
rescue => ex
|
||||
{"errors" => [ex.message]}
|
||||
end
|
||||
end
|
||||
end
|
6
twenty-backend/lib/twenty-backend/graphql/schema.rb
Normal file
6
twenty-backend/lib/twenty-backend/graphql/schema.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
module Twenty::GraphQL
|
||||
class Schema < GraphQL::Schema
|
||||
query Type::Query
|
||||
mutation Type::Mutation
|
||||
end
|
||||
end
|
10
twenty-backend/lib/twenty-backend/graphql/type.rb
Normal file
10
twenty-backend/lib/twenty-backend/graphql/type.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
module Twenty::GraphQL
|
||||
module Type
|
||||
include ::GraphQL::Types
|
||||
end
|
||||
require_relative "type/task_status"
|
||||
require_relative "type/project"
|
||||
require_relative "type/task"
|
||||
require_relative "type/query"
|
||||
require_relative "type/mutation"
|
||||
end
|
12
twenty-backend/lib/twenty-backend/graphql/type/mutation.rb
Normal file
12
twenty-backend/lib/twenty-backend/graphql/type/mutation.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
module Twenty::GraphQL::Type
|
||||
class Mutation < GraphQL::Schema::Object
|
||||
require_relative "../mutation/destroy_task"
|
||||
require_relative "../mutation/complete_task"
|
||||
require_relative "../mutation/create_task"
|
||||
require_relative "../mutation/update_task"
|
||||
field :destroy_task, mutation: Twenty::GraphQL::Mutation::DestroyTask
|
||||
field :complete_task, mutation: Twenty::GraphQL::Mutation::CompleteTask
|
||||
field :create_task, mutation: Twenty::GraphQL::Mutation::CreateTask
|
||||
field :update_task, mutation: Twenty::GraphQL::Mutation::UpdateTask
|
||||
end
|
||||
end
|
10
twenty-backend/lib/twenty-backend/graphql/type/project.rb
Normal file
10
twenty-backend/lib/twenty-backend/graphql/type/project.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
module Twenty::GraphQL::Type
|
||||
class Project < GraphQL::Schema::Object
|
||||
require_relative "task"
|
||||
field :id, ID, null: false
|
||||
field :name, String, null: false
|
||||
field :path, String, null: false
|
||||
field :color, String, null: false
|
||||
field :tasks, [Task], null: false
|
||||
end
|
||||
end
|
23
twenty-backend/lib/twenty-backend/graphql/type/query.rb
Normal file
23
twenty-backend/lib/twenty-backend/graphql/type/query.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
module Twenty::GraphQL::Type
|
||||
class Query < GraphQL::Schema::Object
|
||||
field :find_task, Task, null: true do
|
||||
argument :task_id, Int
|
||||
end
|
||||
field :tasks, [Task], null: false
|
||||
field :projects, [Project], null: false
|
||||
|
||||
def find_task(task_id:)
|
||||
Twenty::Task.find_by(id: task_id)
|
||||
end
|
||||
|
||||
def tasks
|
||||
Twenty::Task
|
||||
.ready
|
||||
.order(updated_at: :desc)
|
||||
end
|
||||
|
||||
def projects
|
||||
Twenty::Project.all
|
||||
end
|
||||
end
|
||||
end
|
11
twenty-backend/lib/twenty-backend/graphql/type/task.rb
Normal file
11
twenty-backend/lib/twenty-backend/graphql/type/task.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
module Twenty::GraphQL::Type
|
||||
class Task < GraphQL::Schema::Object
|
||||
require_relative "project"
|
||||
field :id, Int, null: false
|
||||
field :title, String, null: false
|
||||
field :status, TaskStatus, null: false
|
||||
field :content, String, null: false
|
||||
field :project, Project, null: false
|
||||
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
module Twenty::GraphQL::Type
|
||||
class TaskStatus < GraphQL::Schema::Enum
|
||||
value :ready
|
||||
value :in_progress
|
||||
value :complete
|
||||
end
|
||||
end
|
|
@ -13,10 +13,6 @@ class Twenty::Project < Twenty::Model
|
|||
# Associations
|
||||
has_many :tasks, class_name: "Twenty::Task"
|
||||
|
||||
def to_json(options = {})
|
||||
{id:, name:, path:, color:}.to_json(options)
|
||||
end
|
||||
|
||||
##
|
||||
# @return [String]
|
||||
# The path to a project.
|
||||
|
|
|
@ -15,9 +15,4 @@ class Twenty::Task < Twenty::Model
|
|||
##
|
||||
# Associations
|
||||
belongs_to :project, class_name: "Twenty::Project"
|
||||
|
||||
def as_json(options = {})
|
||||
{id:, title:, content:, status:,
|
||||
project:, created_at:, updated_at:}.as_json(options)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Twenty::Servlet < WEBrick::HTTPServlet::AbstractServlet
|
||||
require_relative "servlet/response"
|
||||
require_relative "servlet/projects"
|
||||
require_relative "servlet/tasks"
|
||||
##
|
||||
# servlets
|
||||
require_relative "servlet/graphql"
|
||||
|
||||
##
|
||||
# mixins
|
||||
require_relative "servlet/mixin/server_mixin"
|
||||
require_relative "servlet/mixin/response_mixin"
|
||||
extend ServerMixin
|
||||
include ResponseMixin
|
||||
|
||||
def parse_body(req, only: [], except: [])
|
||||
body = JSON.parse(req.body)
|
||||
body = only.size > 0 ? body.slice(*only) : body
|
||||
body = except.size > 0 ? body.except(*except) : body
|
||||
body
|
||||
end
|
||||
end
|
||||
|
|
15
twenty-backend/lib/twenty-backend/servlet/graphql.rb
Normal file
15
twenty-backend/lib/twenty-backend/servlet/graphql.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
class Twenty::Servlet::GraphQL < Twenty::Servlet
|
||||
##
|
||||
# POST /servlet/graphql/
|
||||
def do_POST(req, res)
|
||||
params = JSON.parse(req.body)
|
||||
result = Twenty::GraphQL::Schema.execute(
|
||||
params['query'],
|
||||
variables: params['variables'],
|
||||
context: {}
|
||||
)
|
||||
res.headers['content_type'] = 'application/json'
|
||||
res.status = 200
|
||||
res.body = result.to_json
|
||||
end
|
||||
end
|
|
@ -1,42 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Twenty::Servlet::ResponseMixin
|
||||
Response = Twenty::Servlet::Response
|
||||
|
||||
##
|
||||
# Sets 200 response.
|
||||
# @param [WEBrick::HTTPResponse] res
|
||||
# An instance of {WEBrick::HTTPResponse WEBrick::HTTPResponse}
|
||||
# @param [#to_json] body
|
||||
# The response body.
|
||||
# @return [void]
|
||||
def ok(res, body = {})
|
||||
Response.new(res)
|
||||
.set_status(200)
|
||||
.set_body(body)
|
||||
end
|
||||
|
||||
##
|
||||
# Sets 400 response.
|
||||
# @param [WEBrick::HTTPResponse] res
|
||||
# An instance of {WEBrick::HTTPResponse WEBrick::HTTPResponse}
|
||||
# @param [#to_json] body
|
||||
# The response body.
|
||||
# @return [void]
|
||||
def bad_request(res, body = {})
|
||||
Response.new(res)
|
||||
.set_status(400)
|
||||
.set_body({errors: ["Bad request"]}.merge(body))
|
||||
end
|
||||
|
||||
##
|
||||
# Sets 404 response.
|
||||
# @param [WEBrick::HTTPResponse] res
|
||||
# An instance of {WEBrick::HTTPResponse WEBrick::HTTPResponse}
|
||||
# @return [void]
|
||||
def not_found(res)
|
||||
Response.new(res)
|
||||
.set_status(404)
|
||||
.set_body({errors: ["The requested path was not found"]})
|
||||
end
|
||||
end
|
|
@ -10,8 +10,7 @@ module Twenty::Servlet::ServerMixin
|
|||
# Returns an instance of WEBrick::HTTPServer.
|
||||
def server(options = {})
|
||||
server = WEBrick::HTTPServer.new server_options.merge(options)
|
||||
server.mount "/servlet/projects", Twenty::Servlet::Projects
|
||||
server.mount "/servlet/tasks", Twenty::Servlet::Tasks
|
||||
server.mount "/servlet/graphql", Twenty::Servlet::GraphQL
|
||||
server
|
||||
end
|
||||
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Twenty::Servlet::Projects < Twenty::Servlet
|
||||
##
|
||||
# GET /servlet/projects
|
||||
def do_GET(req, res)
|
||||
case req.path_info
|
||||
when ""
|
||||
ok(res, projects: Twenty::Project.all)
|
||||
else
|
||||
not_found(res)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,67 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Twenty::Servlet::Response
|
||||
##
|
||||
# @param [WEBrick::HTTPResponse] res
|
||||
# An instance of WEBrick::HTTPResponse.
|
||||
# @return [Twenty::Servlet::Response]
|
||||
# Returns an instance of Twenty::Servlet::Response.
|
||||
def initialize(res)
|
||||
@res = res
|
||||
set_headers({"content-type" => "application/json"})
|
||||
end
|
||||
|
||||
##
|
||||
# Marks a response as a 404 (Not Found).
|
||||
# @return [Twenty::Servlet::Response]
|
||||
# Returns self.
|
||||
def not_found
|
||||
set_status(404)
|
||||
set_body({errors: ["The requested path was not found"]})
|
||||
end
|
||||
|
||||
##
|
||||
# Sets the response status.
|
||||
# @param [Integer] status
|
||||
# A status code.
|
||||
# @return [Twenty::Servlet::Response]
|
||||
# Returns self.
|
||||
def set_status(status)
|
||||
res.status = status
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# Sets the response body.
|
||||
# @param [#to_json] body
|
||||
# The response body.
|
||||
# @return [Twenty::Servlet::Response]
|
||||
# Returns self.
|
||||
def set_body(body)
|
||||
res.body = default_body_for(res.status).merge(body).to_json
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# Sets the response headers.
|
||||
# @param [#each] headers
|
||||
# The response headers.
|
||||
# @return [Twenty::Servlet::Response]
|
||||
# Returns self.
|
||||
def set_headers(headers)
|
||||
headers.each { res[_1] = _2 }
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :res
|
||||
def default_body_for(status)
|
||||
case status
|
||||
when 200
|
||||
{ok: true, errors: []}
|
||||
else
|
||||
{ok: false, errors: ["Internal server error"]}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,63 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Twenty::Servlet::Tasks < Twenty::Servlet
|
||||
##
|
||||
# GET /servlet/tasks/
|
||||
# GET /servlet/tasks/<id>/
|
||||
def do_GET(req, res)
|
||||
case req.path_info
|
||||
when ""
|
||||
tasks = Twenty::Task.ready.order(updated_at: :desc)
|
||||
ok(res, tasks:)
|
||||
when %r{\A/([\d]+)/?\z}
|
||||
task = Twenty::Task.find_by(id: $1)
|
||||
task ? ok(res, task:) : not_found(res)
|
||||
else
|
||||
not_found(res)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# POST /servlet/tasks/
|
||||
def do_POST(req, res)
|
||||
case req.path_info
|
||||
when ""
|
||||
body = parse_body(req, only: ["title", "content", "project_id"])
|
||||
task = Twenty::Task.new(body)
|
||||
if task.save
|
||||
ok(res, task:)
|
||||
else
|
||||
errors = task.errors.full_messages
|
||||
bad_request(res, errors:)
|
||||
end
|
||||
else
|
||||
not_found(res)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# PUT /servlet/tasks
|
||||
def do_PUT(req, res)
|
||||
case req.path_info
|
||||
when ""
|
||||
body = parse_body(req, except: ["id"])
|
||||
id = parse_body(req, only: ["id"]).fetch("id", nil)
|
||||
task = Twenty::Task.find_by(id:)
|
||||
task&.update(body) ? ok(res, task:) : not_found(res)
|
||||
else
|
||||
not_found(res)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# DELETE /servlet/tasks/<id>/
|
||||
def do_DELETE(req, res)
|
||||
case req.path_info
|
||||
when %r{\A/([\d]+)/?\z}
|
||||
task = Twenty::Task.find_by(id: $1)
|
||||
task.destroy ? ok(res) : not_found(res)
|
||||
else
|
||||
not_found(res)
|
||||
end
|
||||
end
|
||||
end
|
76
twenty-backend/share/twenty-backend/schema.graphql
Normal file
76
twenty-backend/share/twenty-backend/schema.graphql
Normal file
|
@ -0,0 +1,76 @@
|
|||
"""
|
||||
Autogenerated return type of CompleteTask.
|
||||
"""
|
||||
type CompleteTaskPayload {
|
||||
errors: [String!]
|
||||
ok: Boolean
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of CreateTask.
|
||||
"""
|
||||
type CreateTaskPayload {
|
||||
errors: [String!]!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of DestroyTask.
|
||||
"""
|
||||
type DestroyTaskPayload {
|
||||
errors: [String!]
|
||||
ok: Boolean
|
||||
}
|
||||
|
||||
"""
|
||||
An ISO 8601-encoded datetime
|
||||
"""
|
||||
scalar ISO8601DateTime @specifiedBy(url: "https://tools.ietf.org/html/rfc3339")
|
||||
|
||||
type Mutation {
|
||||
completeTask(taskId: Int!): CompleteTaskPayload
|
||||
createTask(input: TaskInput!): CreateTaskPayload
|
||||
destroyTask(taskId: Int!): DestroyTaskPayload
|
||||
updateTask(input: TaskInput!, taskId: Int!): UpdateTaskPayload
|
||||
}
|
||||
|
||||
type Project {
|
||||
color: String!
|
||||
id: ID!
|
||||
name: String!
|
||||
path: String!
|
||||
tasks: [Task!]!
|
||||
}
|
||||
|
||||
type Query {
|
||||
findTask(taskId: Int!): Task
|
||||
projects: [Project!]!
|
||||
tasks: [Task!]!
|
||||
}
|
||||
|
||||
type Task {
|
||||
content: String!
|
||||
id: Int!
|
||||
project: Project!
|
||||
status: TaskStatus!
|
||||
title: String!
|
||||
updatedAt: ISO8601DateTime!
|
||||
}
|
||||
|
||||
input TaskInput {
|
||||
content: String!
|
||||
projectId: Int!
|
||||
title: String!
|
||||
}
|
||||
|
||||
enum TaskStatus {
|
||||
complete
|
||||
in_progress
|
||||
ready
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of UpdateTask.
|
||||
"""
|
||||
type UpdateTaskPayload {
|
||||
errors: [String!]!
|
||||
}
|
|
@ -14,6 +14,7 @@ Gem::Specification.new do |gem|
|
|||
gem.add_runtime_dependency "activerecord", "~> 7.1"
|
||||
gem.add_runtime_dependency "sqlite3", "~> 1.6"
|
||||
gem.add_runtime_dependency "webrick", "~> 1.8"
|
||||
gem.add_runtime_dependency "graphql", "~> 2.2"
|
||||
gem.add_development_dependency "test-unit", "~> 3.5.7"
|
||||
gem.add_development_dependency "standard", "~> 1.13"
|
||||
gem.add_development_dependency "rake", "~> 13.1"
|
||||
|
|
|
@ -90,6 +90,7 @@ GEM
|
|||
rack (>= 3)
|
||||
webrick (~> 1.8)
|
||||
rainpress (1.0.1)
|
||||
rake (13.1.0)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
|
@ -124,6 +125,7 @@ DEPENDENCIES
|
|||
nanoc-live (~> 1.0)
|
||||
nanoc-webpack.rb (~> 0.4)
|
||||
rainpress (~> 1.0)
|
||||
rake (~> 13.0)
|
||||
sass (~> 3.7)
|
||||
twenty-frontend!
|
||||
|
||||
|
|
11
twenty-frontend/Rakefile.rb
Normal file
11
twenty-frontend/Rakefile.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
require "bundler/setup"
|
||||
namespace :schema do
|
||||
desc "Generate src/js/types/schema.ts"
|
||||
task :regen do
|
||||
path = File.join "..", "twenty-backend"
|
||||
Bundler.with_unbundled_env {
|
||||
Dir.chdir(path) { sh "bundle exec rake schema:regen" }
|
||||
}
|
||||
sh "npm exec graphql-codegen"
|
||||
end
|
||||
end
|
7
twenty-frontend/codegen.yml
Normal file
7
twenty-frontend/codegen.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
schema:
|
||||
- ../twenty-backend/share/twenty-backend/schema.graphql
|
||||
|
||||
generates:
|
||||
src/js/types/schema.ts:
|
||||
plugins:
|
||||
- typescript
|
4341
twenty-frontend/package-lock.json
generated
4341
twenty-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -6,18 +6,24 @@
|
|||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apollo/client": "^3.3.21",
|
||||
"@graphql-codegen/cli": "^5.0.0",
|
||||
"@graphql-codegen/typescript": "^4.0.1",
|
||||
"@graphql-codegen/typescript-resolvers": "^4.0.1",
|
||||
"@types/luxon": "^3.3.7",
|
||||
"@types/react": "^18.0.18",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/showdown": "^2.0.6",
|
||||
"eslint": "^8.26.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"graphql": "^16.8.1",
|
||||
"luxon": "^3.4.4",
|
||||
"prettier": "^2.7.1",
|
||||
"react-hook-form": "^7.49.2",
|
||||
"showdown": "^2.1.0",
|
||||
"ts-loader": "^9.3.1",
|
||||
"ts-standard": "^12.0.1",
|
||||
"tslib": "^2.2.0",
|
||||
"typescript": "^4.8.2",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
|
|
|
@ -1,25 +1,31 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { NavBar } from "/components/NavBar";
|
||||
import { useProjects } from "/hooks/useProjects";
|
||||
import { useProjects } from "/hooks/queries/useProjects";
|
||||
import { Project } from "/types/schema";
|
||||
|
||||
export function Projects() {
|
||||
const projects = useProjects();
|
||||
const { data, loading } = useProjects();
|
||||
const projects = data?.projects;
|
||||
|
||||
useEffect(() => {
|
||||
document.title = "Projects";
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="two-columns">
|
||||
<div className="column-1">
|
||||
<NavBar/>
|
||||
<NavBar />
|
||||
</div>
|
||||
<div className="column-2">
|
||||
<div className="panel">
|
||||
<h1>Projects</h1>
|
||||
<div className="panel-body">
|
||||
<ul className="collection">
|
||||
{projects.map((project, i) => {
|
||||
{projects.map((project: Project, i: number) => {
|
||||
return (
|
||||
<li className="item" key={i}>
|
||||
<a href={`/tasks#project_id=${project.id}`}>
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Select } from "/components/forms/Select";
|
||||
import { useUpsertTask } from "/hooks/useUpsertTask";
|
||||
import { useProjects } from "/hooks/useProjects";
|
||||
import { Task } from "/types/schema";
|
||||
import { useCreateTask } from "/hooks/mutations/useCreateTask";
|
||||
import { useUpdateTask } from "/hooks/mutations/useUpdateTask";
|
||||
import { useFindTask } from "/hooks/queries/useFindTask";
|
||||
import { useProjects } from "/hooks/queries/useProjects";
|
||||
import { Task, Project, TaskInput } from "/types/schema";
|
||||
import { rendermd } from "/lib/markdown-utils";
|
||||
import classnames from "classnames";
|
||||
|
||||
type Inputs = {
|
||||
id?: number;
|
||||
title: string;
|
||||
content: string;
|
||||
projectId: number;
|
||||
};
|
||||
|
||||
const DEFAULT_TASK_CONTENT = [
|
||||
"## Subtasks",
|
||||
"",
|
||||
|
@ -26,27 +21,38 @@ const DEFAULT_TASK_CONTENT = [
|
|||
"Add a description here....",
|
||||
].join("\n");
|
||||
|
||||
export function Task({ task }: { task?: Task }) {
|
||||
const { register, handleSubmit, watch, setValue: set } = useForm<Inputs>();
|
||||
const [isEditable, setIsEditable] = useState<boolean>(!task);
|
||||
const upsert = useUpsertTask();
|
||||
const projects = useProjects();
|
||||
export function Task({ taskId }: { taskId?: number }) {
|
||||
const { register, handleSubmit, watch, setValue: set } = useForm<TaskInput>();
|
||||
const [isEditable, setIsEditable] = useState<boolean>(!taskId);
|
||||
const [createTask] = useCreateTask();
|
||||
const [updateTask] = useUpdateTask();
|
||||
const { data: taskData, loading: findingTask } = useFindTask(Number(taskId));
|
||||
const { data: projectsData, loading: findingProjects } = useProjects();
|
||||
const task = taskData?.findTask;
|
||||
const projects = projectsData?.projects;
|
||||
const content = watch("content");
|
||||
const onSave = (input: Inputs) => {
|
||||
upsert({ input }).then(() => {
|
||||
location.href = "/tasks/";
|
||||
});
|
||||
const onSave = (input: TaskInput) => {
|
||||
if (taskId) {
|
||||
updateTask({ variables: { taskId, input } })
|
||||
.then(() => location.href = '/tasks');
|
||||
} else {
|
||||
createTask({ variables: { input } })
|
||||
.then(() => location.href = '/tasks');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const title = task ? task.title : 'New task';
|
||||
const title = task ? task.title : "New task";
|
||||
document.title = title;
|
||||
set("projectId", 1);
|
||||
}, []);
|
||||
|
||||
if (findingProjects || findingTask) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="task" onSubmit={handleSubmit(onSave)}>
|
||||
<input type="hidden" value={task?.id} {...register("id")} />
|
||||
<div className="panel">
|
||||
<div className="panel-header panel-tabs">
|
||||
<ul className="tabs">
|
||||
|
@ -67,7 +73,7 @@ export function Task({ task }: { task?: Task }) {
|
|||
<div className="panel-body">
|
||||
<div>
|
||||
<Select {...register("projectId")} className="form">
|
||||
{projects.map((project, key) => {
|
||||
{projects.map((project: Project, key: number) => {
|
||||
return (
|
||||
<option key={key} value={project.id}>
|
||||
{project.name}
|
||||
|
@ -84,6 +90,7 @@ export function Task({ task }: { task?: Task }) {
|
|||
defaultValue={task?.title}
|
||||
{...register("title", { required: true })}
|
||||
/>
|
||||
<input type="hidden" name="projectId" {...register("projectId")} />
|
||||
</div>
|
||||
{isEditable ? (
|
||||
<>
|
||||
|
|
|
@ -1,58 +1,33 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { useTasks } from "/hooks/useTasks";
|
||||
import { useDestroyTask } from "/hooks/useDestroyTask";
|
||||
import { useTasks } from "/hooks/queries/useTasks";
|
||||
import { useDestroyTask } from "/hooks/mutations/useDestroyTask";
|
||||
import { TrashIcon, DoneIcon } from "/components/Icons";
|
||||
import { NavBar } from "/components/NavBar";
|
||||
import { DateTime } from "luxon";
|
||||
import { Task, TASK_COMPLETE } from "/types/schema";
|
||||
import { useUpsertTask } from "/hooks/useUpsertTask";
|
||||
import { Task } from "/types/schema";
|
||||
import classnames from "classnames";
|
||||
|
||||
type Action = () => Promise<unknown>;
|
||||
type ActionContext = {
|
||||
on: Task;
|
||||
tasks: Task[];
|
||||
setTask: (t: Task | null) => unknown;
|
||||
};
|
||||
import { useCompleteTask } from "/hooks/mutations/useCompleteTask";
|
||||
|
||||
export function Tasks() {
|
||||
const [destroyedTask, setDestroyedTask] = useState<Task | null>(null);
|
||||
const [completedTask, setCompletedTask] = useState<Task | null>(null);
|
||||
const { tasks, setTasks } = useTasks();
|
||||
const upsertTask = useUpsertTask();
|
||||
const destroyTask = useDestroyTask();
|
||||
const perform = (
|
||||
action: Action,
|
||||
{ on: task, tasks, setTask }: ActionContext,
|
||||
) => {
|
||||
action()
|
||||
.then(() => tasks.filter((t: Task) => t.id !== task.id))
|
||||
.then((tasks: Task[]) => {
|
||||
setTask(task);
|
||||
setTimeout(() => {
|
||||
setTasks(tasks);
|
||||
setTask(null);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
const onDestroy = (task: Task) => {
|
||||
const action = () => destroyTask({ id: task.id });
|
||||
perform(action, { on: task, tasks, setTask: setDestroyedTask });
|
||||
};
|
||||
const onComplete = (task: Task) => {
|
||||
const action = () =>
|
||||
upsertTask({ input: { id: task.id, status: TASK_COMPLETE } });
|
||||
perform(action, { on: task, tasks, setTask: setCompletedTask });
|
||||
};
|
||||
const { refetch, loading, data } = useTasks();
|
||||
const tasks = data?.tasks;
|
||||
const [destroyTask] = useDestroyTask();
|
||||
const [completeTask] = useCompleteTask();
|
||||
const [destroyedTask, setDestroyedTask] = useState<Task>(null);
|
||||
const [completedTask, setCompletedTask] = useState<Task>(null);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = "Tasks";
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="two-columns">
|
||||
<div className="column-1">
|
||||
<NavBar/>
|
||||
<NavBar />
|
||||
</div>
|
||||
<div className="column-2">
|
||||
<div className="panel">
|
||||
|
@ -60,11 +35,14 @@ export function Tasks() {
|
|||
<div className="panel-body">
|
||||
<ul className="collection">
|
||||
{tasks.map((task: Task, key: number) => {
|
||||
const { updated_at: updatedAt } = task;
|
||||
const { updatedAt } = task;
|
||||
const datetime = DateTime.fromISO(updatedAt);
|
||||
const wasDestroyed = task === destroyedTask;
|
||||
const wasCompleted = task === completedTask;
|
||||
const classes = { completed: wasCompleted, removed: wasDestroyed };
|
||||
const classes = {
|
||||
completed: wasCompleted,
|
||||
removed: wasDestroyed,
|
||||
};
|
||||
const editHref = `/tasks/edit#id=${task.id}`;
|
||||
return (
|
||||
<li className={classnames("item", classes)} key={key}>
|
||||
|
@ -78,9 +56,12 @@ export function Tasks() {
|
|||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<span className="break"></span>
|
||||
<span className="break" />
|
||||
<span className="tags">
|
||||
<span style={{backgroundColor: task.project.color}} className="tag">
|
||||
<span
|
||||
style={{ backgroundColor: task.project.color }}
|
||||
className="tag"
|
||||
>
|
||||
{task.project.name}
|
||||
</span>
|
||||
</span>
|
||||
|
@ -89,19 +70,25 @@ export function Tasks() {
|
|||
<li>
|
||||
<DoneIcon
|
||||
title="Complete task"
|
||||
onClick={(e: React.MouseEvent) => [
|
||||
e.stopPropagation(),
|
||||
onComplete(task),
|
||||
]}
|
||||
onClick={async (_e: React.MouseEvent) => {
|
||||
await completeTask({
|
||||
variables: { taskId: task.id },
|
||||
});
|
||||
setCompletedTask(task);
|
||||
setTimeout(refetch, 500);
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<TrashIcon
|
||||
title="Delete task"
|
||||
onClick={(e: React.MouseEvent) => [
|
||||
e.stopPropagation(),
|
||||
onDestroy(task),
|
||||
]}
|
||||
onClick={async (_e: React.MouseEvent) => {
|
||||
await destroyTask({
|
||||
variables: { taskId: task.id },
|
||||
});
|
||||
setDestroyedTask(task);
|
||||
setTimeout(refetch, 500);
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
15
twenty-frontend/src/js/hooks/mutations/useCompleteTask.ts
Normal file
15
twenty-frontend/src/js/hooks/mutations/useCompleteTask.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { gql, useMutation } from "@apollo/client";
|
||||
import { CompleteTaskPayload, MutationCompleteTaskArgs } from "/types/schema";
|
||||
|
||||
const GQL = gql`
|
||||
mutation CompleteTask($taskId: Int!) {
|
||||
completeTask(taskId: $taskId) {
|
||||
ok
|
||||
errors
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function useCompleteTask() {
|
||||
return useMutation<CompleteTaskPayload, MutationCompleteTaskArgs>(GQL);
|
||||
}
|
14
twenty-frontend/src/js/hooks/mutations/useCreateTask.ts
Normal file
14
twenty-frontend/src/js/hooks/mutations/useCreateTask.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { CreateTaskPayload, MutationCreateTaskArgs } from "/types/schema";
|
||||
import { gql, useMutation } from "@apollo/client";
|
||||
|
||||
const GQL = gql`
|
||||
mutation CreateTask($input: TaskInput!) {
|
||||
createTask(input: $input) {
|
||||
errors
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function useCreateTask() {
|
||||
return useMutation<CreateTaskPayload, MutationCreateTaskArgs>(GQL);
|
||||
}
|
15
twenty-frontend/src/js/hooks/mutations/useDestroyTask.ts
Normal file
15
twenty-frontend/src/js/hooks/mutations/useDestroyTask.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { gql, useMutation } from "@apollo/client";
|
||||
import { DestroyTaskPayload, MutationDestroyTaskArgs } from "/types/schema";
|
||||
|
||||
const GQL = gql`
|
||||
mutation DestroyTask($taskId: Int!) {
|
||||
destroyTask(taskId: $taskId) {
|
||||
ok
|
||||
errors
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function useDestroyTask() {
|
||||
return useMutation<DestroyTaskPayload, MutationDestroyTaskArgs>(GQL);
|
||||
}
|
14
twenty-frontend/src/js/hooks/mutations/useUpdateTask.ts
Normal file
14
twenty-frontend/src/js/hooks/mutations/useUpdateTask.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { UpdateTaskPayload, MutationUpdateTaskArgs } from "/types/schema";
|
||||
import { gql, useMutation } from "@apollo/client";
|
||||
|
||||
const GQL = gql`
|
||||
mutation UpdateTask($taskId: Int!, $input: TaskInput!) {
|
||||
updateTask(taskId: $taskId, input: $input) {
|
||||
errors
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function useUpdateTask() {
|
||||
return useMutation<UpdateTaskPayload, MutationUpdateTaskArgs>(GQL);
|
||||
}
|
29
twenty-frontend/src/js/hooks/queries/useFindTask.ts
Normal file
29
twenty-frontend/src/js/hooks/queries/useFindTask.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { gql, useQuery } from "@apollo/client";
|
||||
import { Maybe, Task } from "/types/schema";
|
||||
|
||||
const GQL = gql`
|
||||
query Query($taskId: Int!) {
|
||||
findTask(taskId: $taskId) {
|
||||
id
|
||||
title
|
||||
content
|
||||
status
|
||||
project {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function useFindTask(taskId: Maybe<number>) {
|
||||
if (taskId) {
|
||||
return useQuery(GQL, { variables: { taskId } });
|
||||
} else {
|
||||
const result: { data: Maybe<Task>; loading: boolean } = {
|
||||
data: null,
|
||||
loading: false,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
16
twenty-frontend/src/js/hooks/queries/useProjects.ts
Normal file
16
twenty-frontend/src/js/hooks/queries/useProjects.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { useQuery, gql } from "@apollo/client";
|
||||
|
||||
const GQL = gql`
|
||||
query Query {
|
||||
projects {
|
||||
id
|
||||
name
|
||||
path
|
||||
color
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function useProjects() {
|
||||
return useQuery(GQL);
|
||||
}
|
19
twenty-frontend/src/js/hooks/queries/useTasks.ts
Normal file
19
twenty-frontend/src/js/hooks/queries/useTasks.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { useQuery, gql } from "@apollo/client";
|
||||
|
||||
const GQL = gql`
|
||||
query Query {
|
||||
tasks {
|
||||
id
|
||||
title
|
||||
status
|
||||
updatedAt
|
||||
project {
|
||||
color
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function useTasks() {
|
||||
return useQuery(GQL);
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import fetch from "/lib/fetch";
|
||||
|
||||
type Params = {
|
||||
id: number;
|
||||
};
|
||||
|
||||
export function useDestroyTask() {
|
||||
return function ({ id }: Params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reqinit = { method: "DELETE" };
|
||||
return fetch(`/servlet/tasks/${id}`, reqinit)
|
||||
.then(res => res.json())
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { Project } from "/types/schema";
|
||||
|
||||
export function useProjects() {
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/servlet/projects")
|
||||
.then(res => res.json())
|
||||
.then(res => setProjects(res.projects));
|
||||
}, []);
|
||||
|
||||
return projects;
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { Task } from "/types/schema";
|
||||
|
||||
type Result = {
|
||||
setTasks: (tasks: Task[]) => unknown;
|
||||
tasks: Task[];
|
||||
req: () => Promise<Task[]>;
|
||||
};
|
||||
|
||||
export function useTasks(): Result {
|
||||
const [tasks, setTasks] = useState<Task[]>([]);
|
||||
const set = (ary: Task[]) => {
|
||||
setTasks(ary);
|
||||
return ary;
|
||||
};
|
||||
const req = async function (): Promise<Task[]> {
|
||||
return await fetch("/servlet/tasks")
|
||||
.then((res: Response) => res.json())
|
||||
.then((res: { tasks: Task[] }) => set(res.tasks))
|
||||
.catch(() => null);
|
||||
};
|
||||
useEffect(() => {
|
||||
req();
|
||||
}, []);
|
||||
|
||||
return { tasks, setTasks: set, req };
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import { TASK_STATUS } from "/types/schema";
|
||||
import fetch from "/lib/fetch";
|
||||
|
||||
type Params = {
|
||||
id?: number;
|
||||
status?: TASK_STATUS;
|
||||
title?: string;
|
||||
content?: string;
|
||||
projectId?: number;
|
||||
};
|
||||
|
||||
export function useUpsertTask() {
|
||||
const normalize = (input: Params) => {
|
||||
const { id, title, content, status, projectId } = input;
|
||||
return { id, title, content, status, project_id: projectId };
|
||||
};
|
||||
return function ({ input }: { input: Params }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reqinit = {
|
||||
method: input.id ? "PUT" : "POST",
|
||||
body: JSON.stringify(normalize(input)),
|
||||
};
|
||||
return fetch("/servlet/tasks", reqinit)
|
||||
.then(res => res.json())
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
}
|
|
@ -2,7 +2,7 @@ export default async function (
|
|||
path: string,
|
||||
reqinit: RequestInit,
|
||||
): Promise<Response> {
|
||||
return fetch(path, reqinit).then(res => {
|
||||
return await fetch(path, reqinit).then(res => {
|
||||
if (res.status === 200) {
|
||||
return res;
|
||||
} else {
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { Projects } from "/components/Projects";
|
||||
import { ApolloProvider, ApolloClient, InMemoryCache } from "@apollo/client";
|
||||
|
||||
(function () {
|
||||
const root = document.querySelector(".react-mount.projects")!;
|
||||
ReactDOM.createRoot(root).render(<Projects />);
|
||||
const client = new ApolloClient({
|
||||
uri: "/servlet/graphql",
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
ReactDOM.createRoot(root).render(
|
||||
<ApolloProvider client={client}>
|
||||
<Projects />
|
||||
</ApolloProvider>,
|
||||
);
|
||||
})();
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { ApolloProvider } from "@apollo/client";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { Task as Component } from "/components/Task";
|
||||
import { Task } from "/types/schema";
|
||||
import { ApolloClient, InMemoryCache } from "@apollo/client";
|
||||
|
||||
(function () {
|
||||
const params = Object.fromEntries(
|
||||
|
@ -10,10 +12,14 @@ import { Task } from "/types/schema";
|
|||
.split(",")
|
||||
.map(e => e.split("=")),
|
||||
);
|
||||
fetch(`/servlet/tasks/${params.id}`)
|
||||
.then(res => res.json())
|
||||
.then(({ task }: { task: Task }) => {
|
||||
const root = document.querySelector(".react-mount.edit-task")!;
|
||||
ReactDOM.createRoot(root).render(<Component task={task} />);
|
||||
});
|
||||
const client = new ApolloClient({
|
||||
uri: "/servlet/graphql",
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
const root = document.querySelector(".react-mount.edit-task")!;
|
||||
ReactDOM.createRoot(root).render(
|
||||
<ApolloProvider client={client}>
|
||||
<Component taskId={Number(params.id)} />
|
||||
</ApolloProvider>,
|
||||
);
|
||||
})();
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { Task } from "/components/Task";
|
||||
import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";
|
||||
|
||||
(function () {
|
||||
const root = document.querySelector(".react-mount.new-task")!;
|
||||
ReactDOM.createRoot(root).render(<Task />);
|
||||
const client = new ApolloClient({
|
||||
uri: "/servlet/graphql",
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
ReactDOM.createRoot(root).render(
|
||||
<ApolloProvider client={client}>
|
||||
<Task />
|
||||
</ApolloProvider>,
|
||||
);
|
||||
})();
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { Tasks } from "/components/Tasks";
|
||||
|
||||
(function () {
|
||||
const n1 = document.querySelector(".react-mount.tasks")!;
|
||||
ReactDOM.createRoot(n1).render(<Tasks />);
|
||||
const root = document.querySelector(".react-mount.tasks")!;
|
||||
const client = new ApolloClient({
|
||||
uri: "/servlet/graphql",
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
ReactDOM.createRoot(root).render(
|
||||
<ApolloProvider client={client}>
|
||||
<Tasks />
|
||||
</ApolloProvider>,
|
||||
);
|
||||
})();
|
||||
|
|
|
@ -1,21 +1,123 @@
|
|||
export const TASK_READY = "ready";
|
||||
export const TASK_INPROGRESS = "in_progress";
|
||||
export const TASK_COMPLETE = "complete";
|
||||
export type TASK_STATUS = "ready" | "in_progress" | "complete";
|
||||
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]>;
|
||||
};
|
||||
export type MakeEmpty<
|
||||
T extends { [key: string]: unknown },
|
||||
K extends keyof T,
|
||||
> = { [_ in K]?: never };
|
||||
export type Incremental<T> =
|
||||
| T
|
||||
| {
|
||||
[P in keyof T]?: P extends " $fragmentName" | "__typename" ? T[P] : never;
|
||||
};
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export type Scalars = {
|
||||
ID: { input: string; output: string };
|
||||
String: { input: string; output: string };
|
||||
Boolean: { input: boolean; output: boolean };
|
||||
Int: { input: number; output: number };
|
||||
Float: { input: number; output: number };
|
||||
/** An ISO 8601-encoded datetime */
|
||||
ISO8601DateTime: { input: any; output: any };
|
||||
};
|
||||
|
||||
/** Autogenerated return type of CompleteTask. */
|
||||
export type CompleteTaskPayload = {
|
||||
__typename?: "CompleteTaskPayload";
|
||||
errors?: Maybe<Array<Scalars["String"]["output"]>>;
|
||||
ok?: Maybe<Scalars["Boolean"]["output"]>;
|
||||
};
|
||||
|
||||
/** Autogenerated return type of CreateTask. */
|
||||
export type CreateTaskPayload = {
|
||||
__typename?: "CreateTaskPayload";
|
||||
errors: Array<Scalars["String"]["output"]>;
|
||||
};
|
||||
|
||||
/** Autogenerated return type of DestroyTask. */
|
||||
export type DestroyTaskPayload = {
|
||||
__typename?: "DestroyTaskPayload";
|
||||
errors?: Maybe<Array<Scalars["String"]["output"]>>;
|
||||
ok?: Maybe<Scalars["Boolean"]["output"]>;
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: "Mutation";
|
||||
completeTask?: Maybe<CompleteTaskPayload>;
|
||||
createTask?: Maybe<CreateTaskPayload>;
|
||||
destroyTask?: Maybe<DestroyTaskPayload>;
|
||||
updateTask?: Maybe<UpdateTaskPayload>;
|
||||
};
|
||||
|
||||
export type MutationCompleteTaskArgs = {
|
||||
taskId: Scalars["Int"]["input"];
|
||||
};
|
||||
|
||||
export type MutationCreateTaskArgs = {
|
||||
input: TaskInput;
|
||||
};
|
||||
|
||||
export type MutationDestroyTaskArgs = {
|
||||
taskId: Scalars["Int"]["input"];
|
||||
};
|
||||
|
||||
export type MutationUpdateTaskArgs = {
|
||||
input: TaskInput;
|
||||
taskId: Scalars["Int"]["input"];
|
||||
};
|
||||
|
||||
export type Project = {
|
||||
id: number;
|
||||
name: string;
|
||||
path: string;
|
||||
color: string;
|
||||
__typename?: "Project";
|
||||
color: Scalars["String"]["output"];
|
||||
id: Scalars["ID"]["output"];
|
||||
name: Scalars["String"]["output"];
|
||||
path: Scalars["String"]["output"];
|
||||
tasks: Array<Task>;
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: "Query";
|
||||
findTask?: Maybe<Task>;
|
||||
projects: Array<Project>;
|
||||
tasks: Array<Task>;
|
||||
};
|
||||
|
||||
export type QueryFindTaskArgs = {
|
||||
taskId: Scalars["Int"]["input"];
|
||||
};
|
||||
|
||||
export type Task = {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
status: TASK_STATUS;
|
||||
__typename?: "Task";
|
||||
content: Scalars["String"]["output"];
|
||||
id: Scalars["Int"]["output"];
|
||||
project: Project;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
status: TaskStatus;
|
||||
title: Scalars["String"]["output"];
|
||||
updatedAt: Scalars["ISO8601DateTime"]["output"];
|
||||
};
|
||||
|
||||
export type TaskInput = {
|
||||
content: Scalars["String"]["input"];
|
||||
projectId: Scalars["Int"]["input"];
|
||||
title: Scalars["String"]["input"];
|
||||
};
|
||||
|
||||
export enum TaskStatus {
|
||||
Complete = "complete",
|
||||
InProgress = "in_progress",
|
||||
Ready = "ready",
|
||||
}
|
||||
|
||||
/** Autogenerated return type of UpdateTask. */
|
||||
export type UpdateTaskPayload = {
|
||||
__typename?: "UpdateTaskPayload";
|
||||
errors: Array<Scalars["String"]["output"]>;
|
||||
};
|
||||
|
|
|
@ -13,4 +13,5 @@ Gem::Specification.new do |gem|
|
|||
gem.require_paths = ["lib"]
|
||||
gem.summary = "twenty: frontend"
|
||||
gem.description = gem.summary
|
||||
gem.add_development_dependency "rake", "~> 13.0"
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue