Skip to content

Instantly share code, notes, and snippets.

@matijaSos
Last active June 3, 2020 10:15
Show Gist options
  • Save matijaSos/f695df59d430bad1c8208f727a72aa32 to your computer and use it in GitHub Desktop.
Save matijaSos/f695df59d430bad1c8208f727a72aa32 to your computer and use it in GitHub Desktop.
Wasp design 1.0 for TODO app
// 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