Last active
June 3, 2020 10:15
-
-
Save matijaSos/f695df59d430bad1c8208f727a72aa32 to your computer and use it in GitHub Desktop.
Wasp design 1.0 for TODO app
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TODO app in Wasp - try #1 | |
/** | |
Supported features: | |
- There is a page which shows a list of tasks | |
- List of tasks can be refreshed | |
- A new task can be added | |
- Task can be deleted | |
- Task can be updated | |
- There is a user system - user can sign-up/log in (TODO) | |
*/ | |
// TODO (tasks): | |
// - User system? | |
// - Advanced entities? | |
// - ACL? | |
// - Premade components? | |
// - Layouts? | |
// - Local state? | |
// - Complete http request/response control on server. | |
// - Complete http request/response access on client. | |
// - Way to provide "dev secrets", probably via some env files. | |
// ------- main.wasp | |
app TodoApp { | |
favicon: 'assets/todo_favicon.png', | |
auth: ['email/pass'] // If you defined auth, you must have entity User. | |
} | |
auth { | |
entity: User, | |
emailField: email, | |
credentials: { // One method of auth. | |
passwordField: password | |
}, | |
facebook: { // Another method of auth. | |
permissions: ['post'], | |
facebookToken: ENV['FB_TOKEN'] // TODO: Whaaaat -> Ok here we are going to need to provide ENV stuff, secrets! | |
} | |
} | |
// We define the "entry" page of the app. | |
// TODO(matija): do we have to explicitly say it is the entry page? Maybe not because it has the route "/"? | |
// But if it had some other route (e.g. "/main") then we should? Because would've wanted then to redirect "/" -> "/main", right? | |
page MainPage { | |
route: "/", | |
component: MainPageComponent | |
} | |
// TODO: What does page really do? Is it just route + component? Can it do anything more, or would | |
// that just be duplicating components functionality? Aha, ACL is one thing that makes sense on page. | |
page TaskPage (taskId) { // TODO: taskId argument, does all this make sense, it being propagated and so on? | |
route: "/task/:taskId", | |
component: TaskDetails(taskId) | |
} | |
component TaskDetails (taskId) { // Interesting, this is argument/prop to Wasp component. | |
source: "TaskDetails.js", | |
queries: { | |
task: getTask(taskId) // NOTE: Interesting, query here now depends on the prop taskId. | |
} | |
} | |
query getTask (taskId) { | |
uses: [Task], | |
fn: {=js | |
(Task, taskId) => Task.find({id: taskId}) | |
js=} | |
} | |
// Alternative idea: | |
// @uses (Task) | |
// query getTask (taskId) {=js ...js code... js=} | |
// Here we "register" the component to Wasp, so Wasp is "aware" of it. | |
// We specify the source file with the React component, and also the queries that the component | |
// has access to. | |
component MainPageComponent { | |
source: "MainPageComponent.js", | |
queries: { | |
tasks: getAllTasks // Or tasks: () => getAllTasks()? That makes more sense. | |
}, | |
actions: { | |
addNewTask: addNewTask | |
}, | |
user: [ | |
isUserLoggedIn, | |
signUp, | |
logIn, | |
logOut | |
] | |
} | |
// Data model definition. Main thing we will be needing in the future are relations. | |
entity Task { | |
description: String, | |
isDone: Boolean | |
} | |
entity User { | |
email: Email, | |
password: Secret // Is this cool? | |
} | |
query getAllTasks { | |
uses: [Task], | |
fn: {=js | |
(Task) => { | |
return Task.find({}) | |
} | |
js=} | |
// OR source: { import: "getAllTasks", from: "queries.js" } | |
// TODO: Query could have multiple arguments - what then? This then might be called for every cached 'instance' of this query, | |
// which might be expensive, and also complex logic-wise to implement and maintain, hm. But then, maybe this will not be | |
// done often. | |
// We will certainly want: query args, query result, action result, and possibly also action args. | |
// How does Apollo do this? They should have a similar problem. | |
on addNewTask (queryResult, newTask) => { return queryResult.push(newTask) } | |
} | |
action deleteTask (taskId) { | |
uses: [Task], | |
fn: {=js (Task, taskId) => { Task.delete(taskId) } js=} | |
} | |
// TODO: When calling action or query, this call will most likely be happening over http (FE to BE), | |
// what does that mean for implementation? Arguments have to be serializable? | |
action addNewTask (newTaskData) { // Do we need to know argument? Well it is nice to know. But should it be named or not or what? | |
uses: [Task], | |
fn: {=js | |
(Task, newTaskData) => { | |
return Task.insert(newTaskData) | |
} | |
js=} | |
} | |
// Alternative idea: | |
// @uses (Task) | |
// action addNewTask (newTaskData) {=js ...js code... js=} | |
// ------- MainPageComponent.js | |
import React from 'react' | |
export default class MainPageComponent extends React.Component { | |
static propTypes = { | |
tasks: PropTypes.array.object, | |
// TODO: this might not be very nice as a generated code. | |
refreshTasks: PropTypes.function, | |
addNewTask: PropTypes.function, | |
goToPage: PropTypes.function // TODO: We might consider moving this more to "compile time", so you would use goToTaskPage(). | |
isUserLoggedIn: PropTypes.function, // Or bool? | |
logIn: PropTypes.function, | |
signUp: PropTypes.function | |
} | |
render() { | |
if (!this.props.isUserLoggedIn()) { | |
return <AuthScreen logIn=this.props.logIn signUp=this.props.signUp /> | |
} | |
return <div> | |
{ this.props.tasks.map(t => ( | |
<div> | |
<a onClick={goToPage('TaskPage', t.id)}>View task details</a> | |
<span> { t.isDone } </span> | |
<span> { t.description } </span> | |
</div> | |
))} | |
... -> logic/view for obtaining new task data | |
<button onClick={this.addNewTask(...)}>Add new task</button> | |
</div> | |
} | |
} | |
// ------- TaskDetails.js | |
// TODO | |
// TODO: What if we want to refresh certain query that component is listening to? | |
// One option is to do it redux/apollo style -> we call the query directly with | |
// same parameters as it is being listened to, and that will therefore also | |
// refresh the cache and change will be propagated to the component. This means we need to | |
// be able to "inject" the query in component, and call it as a function with arguments, while | |
// also being able to specify that we want it to "forceFetch", and not to use cache. | |
// Other option might be to have some kind of special "refresh" action that we call on | |
// the binded action itself (on 'tasks'), but that is not so standard and I am not sure how | |
// to go about that. | |
// | |
// Also, what about redefining the queries the component is listening to? | |
// Martin thinks that is a separate problem. | |
// | |
// -------- CLI | |
wasp init | |
wasp install | |
wasp start | |
// -------- Ideas | |
// TODO: When inline JS, figure out how to import. Existing system? Smth new? | |
query getAllTasks ~ Task {=js | |
(Task) => { | |
return Task.find({}) | |
} | |
js=} | |
@uses(Task) | |
query getAllTasks (Task) { | |
return Task.find({}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment