Improvements and new version in development #5
			
				
			
		
		
		
	
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -1,14 +1,12 @@ | |||
| package nl.andrewlalis; | ||||
| 
 | ||||
| import nl.andrewlalis.model.Student; | ||||
| import nl.andrewlalis.model.database.DbUtil; | ||||
| import nl.andrewlalis.ui.control.command.CommandExecutor; | ||||
| import nl.andrewlalis.ui.control.command.executables.*; | ||||
| import nl.andrewlalis.command.CommandExecutor; | ||||
| import nl.andrewlalis.command.executables.*; | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| import nl.andrewlalis.ui.view.InitializerApp; | ||||
| import nl.andrewlalis.ui.view.StartView; | ||||
| import nl.andrewlalis.util.CommandLine; | ||||
| import nl.andrewlalis.util.Logging; | ||||
| import org.hibernate.Session; | ||||
| import org.hibernate.SessionFactory; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| import java.util.logging.Logger; | ||||
|  | @ -51,12 +49,15 @@ public class Main { | |||
|                 "© Andrew Lalis (2018), All rights reserved.\n" + | ||||
|                 "Program initialized."); | ||||
| 
 | ||||
|         SessionFactory factory = DbUtil.getSessionFactory(); | ||||
|         Session session = factory.openSession(); | ||||
|         session.beginTransaction(); | ||||
|         System.out.println(session.save(new Student(1, "a", "a@e.com", "git", null))); | ||||
|         session.getTransaction().commit(); | ||||
|         session.close(); | ||||
|         GithubManager manager = new GithubManager(); | ||||
|         StartView startView = new StartView(manager); | ||||
| 
 | ||||
| //        SessionFactory factory = DbUtil.getSessionFactory(); | ||||
| //        Session session = factory.openSession(); | ||||
| //        session.beginTransaction(); | ||||
| //        System.out.println(session.save(new Student(1, "a", "a@e.com", "git", null))); | ||||
| //        session.getTransaction().commit(); | ||||
| //        session.close(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| package nl.andrewlalis.ui.control.command; | ||||
| package nl.andrewlalis.command; | ||||
| 
 | ||||
| import nl.andrewlalis.ui.control.command.executables.ExecutableContext; | ||||
| import nl.andrewlalis.command.executables.ExecutableContext; | ||||
| 
 | ||||
| import java.util.*; | ||||
| import java.util.logging.Logger; | ||||
|  | @ -1,4 +1,4 @@ | |||
| package nl.andrewlalis.ui.control.command; | ||||
| package nl.andrewlalis.command; | ||||
| 
 | ||||
| /** | ||||
|  * Classes which implement this interface tell that they may be 'executed', either via command-line, or through the use | ||||
|  | @ -1,4 +1,4 @@ | |||
| package nl.andrewlalis.ui.control.command.executables; | ||||
| package nl.andrewlalis.command.executables; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| 
 | ||||
|  | @ -1,8 +1,8 @@ | |||
| package nl.andrewlalis.ui.control.command.executables; | ||||
| package nl.andrewlalis.command.executables; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| import nl.andrewlalis.ui.view.dialogs.DefineTaTeamsDialog; | ||||
| import nl.andrewlalis.ui.view.InitializerApp; | ||||
| import nl.andrewlalis.ui.view.dialogs.DefineTaTeamsDialog; | ||||
| 
 | ||||
| /** | ||||
|  * This executable is slightly different from the others, in that it opens up a user interface to make editing TA teams | ||||
|  | @ -1,4 +1,4 @@ | |||
| package nl.andrewlalis.ui.control.command.executables; | ||||
| package nl.andrewlalis.command.executables; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| import nl.andrewlalis.model.StudentTeam; | ||||
|  | @ -1,4 +1,4 @@ | |||
| package nl.andrewlalis.ui.control.command.executables; | ||||
| package nl.andrewlalis.command.executables; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| 
 | ||||
|  | @ -1,6 +1,6 @@ | |||
| package nl.andrewlalis.ui.control.command.executables; | ||||
| package nl.andrewlalis.command.executables; | ||||
| 
 | ||||
| import nl.andrewlalis.ui.control.command.Executable; | ||||
| import nl.andrewlalis.command.Executable; | ||||
| 
 | ||||
| /** | ||||
|  * An object to record a specific executable's execution context (args given), with the ability to re-run the executable | ||||
|  | @ -1,4 +1,4 @@ | |||
| package nl.andrewlalis.ui.control.command.executables; | ||||
| package nl.andrewlalis.command.executables; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| 
 | ||||
|  | @ -1,7 +1,7 @@ | |||
| package nl.andrewlalis.ui.control.command.executables; | ||||
| package nl.andrewlalis.command.executables; | ||||
| 
 | ||||
| import nl.andrewlalis.command.Executable; | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| import nl.andrewlalis.ui.control.command.Executable; | ||||
| 
 | ||||
| /** | ||||
|  * Represents an executable which interacts with github, and therefore needs access to a Github | ||||
|  | @ -1,7 +1,7 @@ | |||
| package nl.andrewlalis.ui.control.command.executables; | ||||
| package nl.andrewlalis.command.executables; | ||||
| 
 | ||||
| import nl.andrewlalis.command.Executable; | ||||
| import nl.andrewlalis.model.error.Error; | ||||
| import nl.andrewlalis.ui.control.command.Executable; | ||||
| import nl.andrewlalis.ui.view.InitializerApp; | ||||
| 
 | ||||
| import java.util.logging.Logger; | ||||
|  | @ -1,4 +1,4 @@ | |||
| package nl.andrewlalis.ui.control.command.executables; | ||||
| package nl.andrewlalis.command.executables; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| import org.kohsuke.github.GHRepository; | ||||
|  | @ -1,7 +1,7 @@ | |||
| package nl.andrewlalis.ui.control.command.executables; | ||||
| package nl.andrewlalis.command.executables; | ||||
| 
 | ||||
| import nl.andrewlalis.command.Executable; | ||||
| import nl.andrewlalis.model.StudentTeam; | ||||
| import nl.andrewlalis.ui.control.command.Executable; | ||||
| import nl.andrewlalis.ui.view.InitializerApp; | ||||
| import nl.andrewlalis.util.FileUtils; | ||||
| 
 | ||||
|  | @ -1,4 +1,4 @@ | |||
| package nl.andrewlalis.ui.control.command.executables; | ||||
| package nl.andrewlalis.command.executables; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| import nl.andrewlalis.model.StudentTeam; | ||||
|  | @ -31,6 +31,7 @@ public class GithubManager { | |||
|     private GitHub github; | ||||
|     private GHOrganization organization; | ||||
|     private String accessToken; | ||||
|     private String organizationName; | ||||
| 
 | ||||
|     /** | ||||
|      * The logger for outputting debug info. | ||||
|  | @ -40,14 +41,82 @@ public class GithubManager { | |||
|         logger.setParent(Logger.getGlobal()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates an empty github manager, which at this point, essentially just allocates the object in memory. | ||||
|      */ | ||||
|     public GithubManager() { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public GithubManager(String organizationName, String accessToken) { | ||||
|         this.organizationName = organizationName; | ||||
|         this.accessToken = accessToken; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Gets the organization that this manager manages. | ||||
|      * @return The Organization object that this manager was constructed for. | ||||
|      * @throws IOException If the organization does not exist or some other error occurs. | ||||
|      */ | ||||
|     private GHOrganization getOrganization() throws IOException { | ||||
|         if (this.github == null || this.organization == null) { | ||||
|             this.github = GitHub.connectUsingOAuth(this.accessToken); | ||||
|             this.organization = this.github.getOrganization(this.organizationName); | ||||
|         } | ||||
| 
 | ||||
|         return this.organization; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Determine if the manager is currently connected to github with the current organization name and access token. | ||||
|      * @return True if the manager is connected, or false otherwise. | ||||
|      */ | ||||
|     public boolean validate() { | ||||
|         try { | ||||
|             this.github = GitHub.connectUsingOAuth(accessToken); | ||||
|             this.organization = this.github.getOrganization(organizationName); | ||||
|             this.organization = this.getOrganization(); | ||||
|             return this.organization != null; | ||||
|         } catch (IOException e) { | ||||
|             logger.severe("Unable to make a GithubManager with organization name: " + organizationName + " and access token: " + accessToken); | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void setOrganizationName(String name) { | ||||
|         this.organizationName = name; | ||||
|         this.github = null; | ||||
|         this.organization = null; | ||||
|     } | ||||
| 
 | ||||
|     public void setAccessToken(String token) { | ||||
|         this.accessToken = token; | ||||
|         this.github = null; | ||||
|         this.organization = null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Determines if a repository exists in the current organization. | ||||
|      * @param repoName The name of the repository. | ||||
|      * @return True if the repository exists, false otherwise. | ||||
|      */ | ||||
|     public boolean repoExists(String repoName) { | ||||
|         try { | ||||
|             GHRepository repo = this.getOrganization().getRepository(repoName); | ||||
|             return repo != null; | ||||
|         } catch (IOException e) { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Determines if a team exists in the current organization. | ||||
|      * @param teamName The name of the team. | ||||
|      * @return True if the team exists, false otherwise. | ||||
|      */ | ||||
|     public boolean teamExists(String teamName) { | ||||
|         try { | ||||
|             GHTeam team = this.getOrganization().getTeamByName(teamName); | ||||
|             return team != null; | ||||
|         } catch (IOException e) { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -59,7 +128,7 @@ public class GithubManager { | |||
|     public List<GHRepository> listReposWithPrefix(String substring) { | ||||
|         List<GHRepository> repos = new ArrayList<>(); | ||||
|         try { | ||||
|             List<GHRepository> allRepos = this.organization.listRepositories().asList(); | ||||
|             List<GHRepository> allRepos = this.getOrganization().listRepositories().asList(); | ||||
|             for (GHRepository repo : allRepos) { | ||||
|                 if (repo.getName().contains(substring)) { | ||||
|                     repos.add(repo); | ||||
|  | @ -81,7 +150,7 @@ public class GithubManager { | |||
|     public GHRepository getRepository(String name) { | ||||
|         System.out.println(name); | ||||
|         try { | ||||
|             return this.organization.getRepository(name); | ||||
|             return this.getOrganization().getRepository(name); | ||||
|         } catch (IOException e) { | ||||
|             logger.severe("No repository with name: " + name + " exists."); | ||||
|             e.printStackTrace(); | ||||
|  | @ -97,7 +166,7 @@ public class GithubManager { | |||
|         List<TATeam> teams = new ArrayList<>(); | ||||
|         try { | ||||
|             Random rand = new Random(); | ||||
|             for (Map.Entry<String, GHTeam> entry : this.organization.getTeams().entrySet()) { | ||||
|             for (Map.Entry<String, GHTeam> entry : this.getOrganization().getTeams().entrySet()) { | ||||
|                 TATeam team = new TATeam(entry.getKey(), entry.getValue().getId()); | ||||
|                 team.setGithubTeam(entry.getValue()); | ||||
|                 for (GHUser user : entry.getValue().listMembers().asList()) { | ||||
|  | @ -119,7 +188,7 @@ public class GithubManager { | |||
|     public List<TeachingAssistant> getMembers() { | ||||
|         List<TeachingAssistant> teachingAssistants = new ArrayList<>(); | ||||
|         try { | ||||
|             for (GHUser member : this.organization.listMembers().asList()) { | ||||
|             for (GHUser member : this.getOrganization().listMembers().asList()) { | ||||
|                 teachingAssistants.add(new TeachingAssistant(-1, member.getName(), member.getEmail(), member.getLogin())); | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|  | @ -137,9 +206,9 @@ public class GithubManager { | |||
|      * @throws IOException If an HTTP request failed. | ||||
|      */ | ||||
|     public void setupAssignmentsRepo(String assignmentsRepoName, String description, String allTeachingAssistants) throws IOException { | ||||
|         GHTeam team = this.organization.getTeamByName(allTeachingAssistants); | ||||
|         GHTeam team = this.getOrganization().getTeamByName(allTeachingAssistants); | ||||
|         // Check if the repository already exists. | ||||
|         GHRepository existingRepo = this.organization.getRepository(assignmentsRepoName); | ||||
|         GHRepository existingRepo = this.getOrganization().getRepository(assignmentsRepoName); | ||||
|         if (existingRepo != null) { | ||||
|             existingRepo.delete(); | ||||
|             logger.fine("Deleted pre-existing assignments repository."); | ||||
|  | @ -174,9 +243,10 @@ public class GithubManager { | |||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         GHRepository repo = this.createRepository(team.generateUniqueName(prefix), taTeam.getGithubTeam(), team.generateRepoDescription(), false, true, true); | ||||
| 
 | ||||
|         if (repo == null) { | ||||
|         GHRepository repo; | ||||
|         try { | ||||
|             repo = this.createRepository(team.generateUniqueName(prefix), taTeam.getGithubTeam(), team.generateRepoDescription(), false, true, true); | ||||
|         } catch (IOException e) { | ||||
|             logger.severe("Repository for student team " + team.getNumber() + " could not be created."); | ||||
|             return; | ||||
|         } | ||||
|  | @ -195,17 +265,22 @@ public class GithubManager { | |||
|      * @param substring The substring which repository names should contain to be deleted. | ||||
|      */ | ||||
|     public void deleteAllRepositories(String substring) { | ||||
|         List<GHRepository> repositories = this.organization.listRepositories().asList(); | ||||
|         for (GHRepository repo : repositories) { | ||||
|             if (repo.getName().contains(substring)) { | ||||
|                 try { | ||||
|                     repo.delete(); | ||||
|                     logger.info("Deleted repository: " + repo.getName()); | ||||
|                 } catch (IOException e) { | ||||
|                     logger.severe("Could not delete repository: " + repo.getName()); | ||||
|                     e.printStackTrace(); | ||||
|         try { | ||||
|             List<GHRepository> repositories = this.getOrganization().listRepositories().asList(); | ||||
|             for (GHRepository repo : repositories) { | ||||
|                 if (repo.getName().contains(substring)) { | ||||
|                     try { | ||||
|                         repo.delete(); | ||||
|                         logger.info("Deleted repository: " + repo.getName()); | ||||
|                     } catch (IOException e) { | ||||
|                         logger.severe("Could not delete repository: " + repo.getName()); | ||||
|                         e.printStackTrace(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             logger.severe("Could not get Organization for listing repositories."); | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -214,11 +289,16 @@ public class GithubManager { | |||
|      * @param sub Any repository containing this substring will be archived. | ||||
|      */ | ||||
|     public void archiveAllRepositories(String sub) { | ||||
|         List<GHRepository> repositories = this.organization.listRepositories().asList(); | ||||
|         for (GHRepository repo : repositories) { | ||||
|             if (repo.getName().contains(sub)) { | ||||
|                 archiveRepository(repo); | ||||
|         try { | ||||
|             List<GHRepository> repositories = this.getOrganization().listRepositories().asList(); | ||||
|             for (GHRepository repo : repositories) { | ||||
|                 if (repo.getName().contains(sub)) { | ||||
|                     archiveRepository(repo); | ||||
|                 } | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             logger.severe("Could not get Organization for listing repositories."); | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -339,25 +419,20 @@ public class GithubManager { | |||
|      * @param hasWiki Whether the repo has a wiki enabled. | ||||
|      * @param hasIssues Whether the repo has issues enabled. | ||||
|      * @param isPrivate Whether or not the repository is private. | ||||
|      * @return The repository that was created, or null if it could not be created. | ||||
|      * @return The repository that was created. | ||||
|      * @throws IOException If an exception occurred while sending a request. | ||||
|      */ | ||||
|     private GHRepository createRepository(String name, GHTeam taTeam, String description, boolean hasWiki, boolean hasIssues, boolean isPrivate){ | ||||
|         try { | ||||
|             GHCreateRepositoryBuilder builder = this.organization.createRepository(name); | ||||
|             builder.team(taTeam); | ||||
|             builder.wiki(hasWiki); | ||||
|             builder.issues(hasIssues); | ||||
|             builder.description(description); | ||||
|             builder.gitignoreTemplate("Java"); | ||||
|             builder.private_(isPrivate); | ||||
|             GHRepository repo = builder.create(); | ||||
|             logger.fine("Created repository: " + repo.getName()); | ||||
|             return repo; | ||||
|         } catch (IOException e) { | ||||
|             logger.severe("Could not create repository: " + name); | ||||
|             e.printStackTrace(); | ||||
|             return null; | ||||
|         } | ||||
|     private GHRepository createRepository(String name, GHTeam taTeam, String description, boolean hasWiki, boolean hasIssues, boolean isPrivate) throws IOException { | ||||
|         GHCreateRepositoryBuilder builder = this.getOrganization().createRepository(name); | ||||
|         builder.team(taTeam); | ||||
|         builder.wiki(hasWiki); | ||||
|         builder.issues(hasIssues); | ||||
|         builder.description(description); | ||||
|         builder.gitignoreTemplate("Java"); | ||||
|         builder.private_(isPrivate); | ||||
|         GHRepository repo = builder.create(); | ||||
|         logger.fine("Created repository: " + repo.getName()); | ||||
|         return repo; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,9 +0,0 @@ | |||
| package nl.andrewlalis.model; | ||||
| 
 | ||||
| public abstract class DatabaseObject { | ||||
| 
 | ||||
|     public abstract DatabaseObject retrieve(); | ||||
| 
 | ||||
|     public abstract boolean store(); | ||||
| 
 | ||||
| } | ||||
|  | @ -1,6 +1,6 @@ | |||
| package nl.andrewlalis.ui.control.listeners; | ||||
| 
 | ||||
| import nl.andrewlalis.ui.control.command.CommandExecutor; | ||||
| import nl.andrewlalis.command.CommandExecutor; | ||||
| import nl.andrewlalis.ui.view.InitializerApp; | ||||
| 
 | ||||
| import javax.swing.*; | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| package nl.andrewlalis.ui.control.listeners; | ||||
| 
 | ||||
| import nl.andrewlalis.ui.control.command.CommandExecutor; | ||||
| import nl.andrewlalis.command.CommandExecutor; | ||||
| 
 | ||||
| import javax.swing.*; | ||||
| import java.awt.event.KeyEvent; | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| package nl.andrewlalis.ui.control.listeners; | ||||
| 
 | ||||
| import nl.andrewlalis.ui.control.command.CommandExecutor; | ||||
| import nl.andrewlalis.command.CommandExecutor; | ||||
| import nl.andrewlalis.ui.view.InitializerApp; | ||||
| 
 | ||||
| import java.awt.event.ActionEvent; | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| package nl.andrewlalis.ui.control.listeners; | ||||
| 
 | ||||
| import nl.andrewlalis.ui.control.command.CommandExecutor; | ||||
| import nl.andrewlalis.command.CommandExecutor; | ||||
| import nl.andrewlalis.ui.view.InitializerApp; | ||||
| 
 | ||||
| import java.awt.event.ActionEvent; | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| package nl.andrewlalis.ui.control.listeners; | ||||
| 
 | ||||
| import nl.andrewlalis.ui.control.command.CommandExecutor; | ||||
| import nl.andrewlalis.command.CommandExecutor; | ||||
| import nl.andrewlalis.ui.view.InitializerApp; | ||||
| 
 | ||||
| import javax.swing.*; | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| package nl.andrewlalis.ui.control.listeners; | ||||
| 
 | ||||
| import nl.andrewlalis.ui.control.command.CommandExecutor; | ||||
| import nl.andrewlalis.command.CommandExecutor; | ||||
| import nl.andrewlalis.ui.view.InitializerApp; | ||||
| 
 | ||||
| import java.awt.event.ActionListener; | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| package nl.andrewlalis.ui.control.listeners; | ||||
| 
 | ||||
| import nl.andrewlalis.ui.control.command.CommandExecutor; | ||||
| import nl.andrewlalis.command.CommandExecutor; | ||||
| import nl.andrewlalis.ui.view.InitializerApp; | ||||
| 
 | ||||
| import javax.swing.*; | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| package nl.andrewlalis.ui.control.listeners; | ||||
| 
 | ||||
| import nl.andrewlalis.ui.control.command.CommandExecutor; | ||||
| import nl.andrewlalis.command.CommandExecutor; | ||||
| import nl.andrewlalis.ui.view.InitializerApp; | ||||
| 
 | ||||
| import javax.swing.*; | ||||
|  |  | |||
|  | @ -0,0 +1,68 @@ | |||
| package nl.andrewlalis.ui.control.listeners; | ||||
| 
 | ||||
| import nl.andrewlalis.ui.view.AbstractView; | ||||
| 
 | ||||
| import java.awt.event.ActionEvent; | ||||
| import java.awt.event.ActionListener; | ||||
| import java.awt.event.WindowAdapter; | ||||
| import java.awt.event.WindowEvent; | ||||
| 
 | ||||
| /** | ||||
|  * The ViewChangeListener is attached to buttons which should change the view to a new view. With this listener, one | ||||
|  * needs to simply give the previous view, and the next view, and | ||||
|  */ | ||||
| public class ViewChangeListener implements ActionListener { | ||||
| 
 | ||||
|     protected AbstractView previousView; | ||||
|     protected AbstractView newView; | ||||
| 
 | ||||
|     public ViewChangeListener(AbstractView previousView, AbstractView newView) { | ||||
|         this.previousView = previousView; | ||||
|         this.newView = newView; | ||||
|         this.newView.addWindowListener(new WindowAdapter() { | ||||
|             @Override | ||||
|             public void windowClosing(WindowEvent windowEvent) { | ||||
|                 back(); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This method is called just before the view is changed. | ||||
|      * @return True if the change should happen, or false if some validation or check prevents the user from moving to | ||||
|      * the next view. | ||||
|      */ | ||||
|     protected boolean beforeChange() { | ||||
|         // Child classes can implement extra behavior here. | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Defines some default behavior for switching to a new view. | ||||
|      * @param actionEvent The event which triggered this action. | ||||
|      */ | ||||
|     @Override | ||||
|     public void actionPerformed(ActionEvent actionEvent) { | ||||
|         if (this.beforeChange()) { | ||||
|             this.forward(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Goes to the new view, and hides the previous view. | ||||
|      */ | ||||
|     private void forward() { | ||||
|         this.previousView.setVisible(false); | ||||
|         this.newView.reset(); | ||||
|         this.newView.setVisible(true); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Goes 'back in time', or rather, hides the current view and moves back to the one which sent us here. | ||||
|      */ | ||||
|     private void back() { | ||||
|         this.previousView.reset(); | ||||
|         this.previousView.setVisible(true); | ||||
|         this.newView.setVisible(false); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,59 @@ | |||
| package nl.andrewlalis.ui.control.listeners.create_assignments_view; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| import nl.andrewlalis.ui.control.listeners.ViewChangeListener; | ||||
| import nl.andrewlalis.ui.view.AbstractView; | ||||
| import nl.andrewlalis.ui.view.CreateAssignmentsView; | ||||
| 
 | ||||
| import javax.swing.*; | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| /** | ||||
|  * Listens for when the user clicks 'next' in the CreateAssignmentsView. This listener is responsible for checking that | ||||
|  * the user enters a correct repository name, or if not, asks if the user wishes to create the repository with that | ||||
|  * name. | ||||
|  */ | ||||
| public class NextListener extends ViewChangeListener { | ||||
| 
 | ||||
|     public NextListener(AbstractView previousView, AbstractView newView) { | ||||
|         super(previousView, newView); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Validate that the repository the user has entered exists in the organization. | ||||
|      * @return True if the repository exists, or if the user creates a repository with that name, or false if an | ||||
|      * Assignments repository was not created. | ||||
|      */ | ||||
|     @Override | ||||
|     protected boolean beforeChange() { | ||||
|         CreateAssignmentsView assignmentsView = (CreateAssignmentsView) this.previousView; | ||||
|         String repoName = assignmentsView.getRepositoryName(); | ||||
|         GithubManager manager = assignmentsView.getGithubManager(); | ||||
|         if (manager.repoExists(repoName)) { | ||||
|             return true; | ||||
|         } else { | ||||
|             int reply = JOptionPane.showConfirmDialog( | ||||
|                     assignmentsView, | ||||
|                     "The repository you gave does not exist.\nWould you like to create it?", | ||||
|                     "Create new repository?", | ||||
|                     JOptionPane.YES_NO_OPTION); | ||||
|             if (reply == JOptionPane.YES_OPTION) { | ||||
|                 try { | ||||
|                     assignmentsView.getGithubManager().setupAssignmentsRepo(repoName, "", this.getTeachingAssistantsTeamName()); | ||||
|                     return true; | ||||
|                 } catch (IOException e) { | ||||
|                     //e.printStackTrace(); | ||||
|                     JOptionPane.showMessageDialog(assignmentsView, "Could not create repository:\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); | ||||
|                     return false; | ||||
|                 } | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private String getTeachingAssistantsTeamName() { | ||||
|         String name = JOptionPane.showInputDialog(this.previousView, "Please enter (exactly) the name of Github team\nthat contains all teaching assistants.", "Select TA Team", JOptionPane.QUESTION_MESSAGE); | ||||
|         return name; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,27 @@ | |||
| package nl.andrewlalis.ui.control.listeners.start_view; | ||||
| 
 | ||||
| import nl.andrewlalis.ui.control.listeners.ViewChangeListener; | ||||
| import nl.andrewlalis.ui.view.AbstractView; | ||||
| import nl.andrewlalis.ui.view.StartView; | ||||
| 
 | ||||
| /** | ||||
|  * Listener for when the user intends to create repositories for a new course. | ||||
|  */ | ||||
| public class CreateAssignmentsRepoListener extends ViewChangeListener { | ||||
| 
 | ||||
|     public CreateAssignmentsRepoListener(AbstractView previousView, AbstractView newView) { | ||||
|         super(previousView, newView); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * All that needs to be done here is check that the github manager can work with the given info. | ||||
|      * @return True if the github manager accepts the organization name and access token, false otherwise. | ||||
|      */ | ||||
|     @Override | ||||
|     protected boolean beforeChange() { | ||||
|         StartView startView = (StartView) this.previousView; | ||||
|         startView.getGithubManager().setOrganizationName(startView.getOrganizationName()); | ||||
|         startView.getGithubManager().setAccessToken(startView.getAccessToken()); | ||||
|         return startView.getGithubManager().validate(); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,72 @@ | |||
| package nl.andrewlalis.ui.view; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| 
 | ||||
| import javax.swing.*; | ||||
| import java.awt.*; | ||||
| 
 | ||||
| /** | ||||
|  * All views in the application will extend from this view, as a means of simplifying and organizing how visual | ||||
|  * components are made. | ||||
|  */ | ||||
| public abstract class AbstractView extends JFrame { | ||||
| 
 | ||||
|     /** | ||||
|      * A GithubManager object which can be used to interact with github. | ||||
|      */ | ||||
|     private GithubManager githubManager; | ||||
| 
 | ||||
|     /** | ||||
|      * Initializes the view by packing the content pane as it is defined by any child, and setting some generic swing | ||||
|      * values. | ||||
|      * @param title The window's title. | ||||
|      * @param startVisible Whether or not to start the view as visible. | ||||
|      * @param defaultCloseOperation What to do when the user closes the window. | ||||
|      * @param preferredSize The preferred size of the view. | ||||
|      */ | ||||
|     AbstractView(String title, boolean startVisible, int defaultCloseOperation, Dimension preferredSize, GithubManager githubManager) { | ||||
|         super(title); | ||||
|         this.githubManager = githubManager; | ||||
|         this.setContentPane(this.buildContentPane()); | ||||
|         this.setDefaultCloseOperation(defaultCloseOperation); | ||||
|         if (preferredSize != null) { | ||||
|             this.setSize(preferredSize); | ||||
|         } | ||||
|         this.setLocationRelativeTo(null); | ||||
|         this.pack(); | ||||
|         this.setVisible(startVisible); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Constructs this view. Child classes will define how the content pane is constructed by returning that content | ||||
|      * pane here. | ||||
|      * @return The content pane containing the view to be rendered. | ||||
|      */ | ||||
|     protected abstract JPanel buildContentPane(); | ||||
| 
 | ||||
|     /** | ||||
|      * Resets this view and all form components within it. It is the responsibility of child classes to define how to | ||||
|      * reset themselves. | ||||
|      */ | ||||
|     public void reset() { | ||||
|         // Child classes can define custom behavior here. | ||||
|     } | ||||
| 
 | ||||
|     public GithubManager getGithubManager() { | ||||
|         return githubManager; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Generates a text input field panel. | ||||
|      * @param labelText The text for the label above the panel. | ||||
|      * @param textField A reference to the text field that is used in the panel. | ||||
|      * @return A JPanel containing the label and text field. | ||||
|      */ | ||||
|     final JPanel generateTextFieldPanel(String labelText, JTextField textField) { | ||||
|         JPanel newPanel = new JPanel(new BorderLayout()); | ||||
|         newPanel.add(new JLabel(labelText), BorderLayout.NORTH); | ||||
|         newPanel.add(textField); | ||||
|         newPanel.setBorder(BorderFactory.createEmptyBorder(5, 2, 5, 2)); | ||||
|         return newPanel; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,45 @@ | |||
| package nl.andrewlalis.ui.view; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| import nl.andrewlalis.ui.control.listeners.create_assignments_view.NextListener; | ||||
| 
 | ||||
| import javax.swing.*; | ||||
| import java.awt.*; | ||||
| 
 | ||||
| /** | ||||
|  * In this view, the user will enter the name of an assignments repository to use for the course, or allows the user to | ||||
|  * create a new one. | ||||
|  * | ||||
|  * Once the user is here, it is guaranteed that the github manager has been validated. | ||||
|  */ | ||||
| public class CreateAssignmentsView extends AbstractView { | ||||
| 
 | ||||
|     private JTextField repositoryNameField; | ||||
| 
 | ||||
|     public CreateAssignmentsView(GithubManager manager) { | ||||
|         super("Create/Set Assignments Repository", | ||||
|                 false, | ||||
|                 DISPOSE_ON_CLOSE, | ||||
|                 null, | ||||
|                 manager); | ||||
|     } | ||||
| 
 | ||||
|     public String getRepositoryName() { | ||||
|         return this.repositoryNameField.getText(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected JPanel buildContentPane() { | ||||
|         JPanel contentPane = new JPanel(); | ||||
| 
 | ||||
|         contentPane.setLayout(new BorderLayout()); | ||||
|         this.repositoryNameField = new JTextField(); | ||||
|         contentPane.add(this.generateTextFieldPanel("Repository name:", this.repositoryNameField), BorderLayout.CENTER); | ||||
| 
 | ||||
|         JButton nextButton = new JButton("Next"); | ||||
|         nextButton.addActionListener(new NextListener(this, new InputStudentsFileView(this.getGithubManager()))); | ||||
|         contentPane.add(nextButton, BorderLayout.SOUTH); | ||||
| 
 | ||||
|         return contentPane; | ||||
|     } | ||||
| } | ||||
|  | @ -1,8 +1,8 @@ | |||
| package nl.andrewlalis.ui.view; | ||||
| 
 | ||||
| import nl.andrewlalis.command.CommandExecutor; | ||||
| import nl.andrewlalis.model.Organization; | ||||
| import nl.andrewlalis.ui.control.OutputTextHandler; | ||||
| import nl.andrewlalis.ui.control.command.CommandExecutor; | ||||
| import nl.andrewlalis.ui.control.listeners.*; | ||||
| 
 | ||||
| import javax.swing.*; | ||||
|  | @ -129,11 +129,6 @@ public class InitializerApp extends JFrame { | |||
|         commonActionsPanel.add(this.generateButtonPanel("Delegate Student Teams", new DelegateStudentTeamsListener(this.executor, this))); | ||||
|         commonActionsPanel.add(this.generateButtonPanel("Generate Assignments Repo", new GenerateAssignmentsRepoListener(this.executor, this))); | ||||
| 
 | ||||
|         // TODO: Enable this once the define teams dialog is complete. | ||||
| //        JButton defineTaTeamsButton = new JButton("Define TA Teams"); | ||||
| //        defineTaTeamsButton.addActionListener(new DefineTaTeamsListener(this.executor, this)); | ||||
| //        commonActionsPanel.add(f); | ||||
| 
 | ||||
|         commonActionsPanel.add(this.generateButtonPanel("Delete Repos", new DeleteReposListener(this.executor, this))); | ||||
| 
 | ||||
|         // Extra panel to push buttons to the top. | ||||
|  |  | |||
|  | @ -0,0 +1,33 @@ | |||
| package nl.andrewlalis.ui.view; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| 
 | ||||
| import javax.swing.*; | ||||
| import java.awt.*; | ||||
| 
 | ||||
| /** | ||||
|  * In this view, the user will select a file to read a list of students from, and generates the list of teams from that. | ||||
|  */ | ||||
| public class InputStudentsFileView extends AbstractView { | ||||
| 
 | ||||
|     InputStudentsFileView(GithubManager manager) { | ||||
|         super("Input Students CSV", | ||||
|                 false, | ||||
|                 DISPOSE_ON_CLOSE, | ||||
|                 null, | ||||
|                 manager); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected JPanel buildContentPane() { | ||||
|         JPanel contentPane = new JPanel(new BorderLayout()); | ||||
| 
 | ||||
|         JButton selectFileButton = new JButton("Select File"); | ||||
|         contentPane.add(selectFileButton, BorderLayout.CENTER); | ||||
| 
 | ||||
|         JButton doneButton = new JButton("Done"); | ||||
|         contentPane.add(doneButton, BorderLayout.SOUTH); | ||||
| 
 | ||||
|         return contentPane; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,63 @@ | |||
| package nl.andrewlalis.ui.view; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| import nl.andrewlalis.ui.control.listeners.start_view.CreateAssignmentsRepoListener; | ||||
| 
 | ||||
| import javax.swing.*; | ||||
| import java.awt.*; | ||||
| 
 | ||||
| /** | ||||
|  * At this view, the user is asked to first enter the name of the organization, and the access token they created for | ||||
|  * their authenticated Github account. | ||||
|  * | ||||
|  * Then, the user must choose whether they are starting a new course setup, or managing an existing one. | ||||
|  * | ||||
|  * If they choose to start a new course, they are taken to the AssignmentsRepoView, otherwise if they want to manage | ||||
|  * an existing course, they are taken to the ManagementView. | ||||
|  */ | ||||
| public class StartView extends AbstractView { | ||||
| 
 | ||||
|     // Fields which hold information needed by the Github Manager. | ||||
|     private JTextField organizationNameField; | ||||
|     private JTextField accessTokenField; | ||||
| 
 | ||||
|     public StartView(GithubManager githubManager) { | ||||
|         super("Github Initializer Startup", | ||||
|                 true, | ||||
|                 DISPOSE_ON_CLOSE, | ||||
|                 null, | ||||
|                 githubManager); | ||||
|     } | ||||
| 
 | ||||
|     public String getOrganizationName() { | ||||
|         return this.organizationNameField.getText(); | ||||
|     } | ||||
| 
 | ||||
|     public String getAccessToken() { | ||||
|         return this.accessTokenField.getText(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected JPanel buildContentPane() { | ||||
|         JPanel contentPane = new JPanel(new BorderLayout()); | ||||
| 
 | ||||
|         JPanel infoInputPanel = new JPanel(); | ||||
|         infoInputPanel.setLayout(new BoxLayout(infoInputPanel, BoxLayout.PAGE_AXIS)); | ||||
|         this.organizationNameField = new JTextField(); | ||||
|         infoInputPanel.add(this.generateTextFieldPanel("Organization name:", this.organizationNameField)); | ||||
|         this.accessTokenField = new JTextField(); | ||||
|         infoInputPanel.add(this.generateTextFieldPanel("Access token:", this.accessTokenField)); | ||||
| 
 | ||||
|         JPanel buttonsPanel = new JPanel(); | ||||
|         JButton assignmentsViewButton = new JButton("Start New Course"); | ||||
|         assignmentsViewButton.addActionListener(new CreateAssignmentsRepoListener(this, new CreateAssignmentsView(this.getGithubManager()))); | ||||
|         JButton managementViewButton = new JButton("Manage Existing Course"); | ||||
| 
 | ||||
|         buttonsPanel.add(assignmentsViewButton); | ||||
|         buttonsPanel.add(managementViewButton); | ||||
| 
 | ||||
|         contentPane.add(infoInputPanel, BorderLayout.CENTER); | ||||
|         contentPane.add(buttonsPanel, BorderLayout.SOUTH); | ||||
|         return contentPane; | ||||
|     } | ||||
| } | ||||
|  | @ -1,19 +0,0 @@ | |||
| INSERT INTO person_types (id, name) | ||||
| VALUES (0, 'student'), | ||||
|        (1, 'teaching-assistant'), | ||||
|        (2, 'professor'); | ||||
| 
 | ||||
| INSERT INTO team_types (id, name) | ||||
| VALUES (0, 'student_team'), | ||||
|        (1, 'teaching_assistant_team'), | ||||
|        (2, 'all_teaching_assistants'), | ||||
|        (3, 'none'); | ||||
| 
 | ||||
| INSERT INTO teams (id, team_type_id) | ||||
| VALUES (1000000, 3), -- None team for all students or TA's without a team. | ||||
|        (1000001, 2); -- Team for all teaching assistants. | ||||
| 
 | ||||
| INSERT INTO error_types (id, name) | ||||
| VALUES (0, 'team_error'), | ||||
|        (1, 'person_error'), | ||||
|        (2, 'system_error'); | ||||
|  | @ -1,153 +0,0 @@ | |||
| PRAGMA foreign_keys = TRUE; | ||||
| PRAGMA writable_schema = 1; | ||||
| DELETE FROM sqlite_master WHERE type IN ('table', 'index', 'trigger'); | ||||
| PRAGMA writable_schema = 0; | ||||
| VACUUM; | ||||
| 
 | ||||
| -- Basic schema design. | ||||
| CREATE TABLE IF NOT EXISTS person_types ( | ||||
|   id INTEGER PRIMARY KEY, | ||||
|   name TEXT NOT NULL UNIQUE | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE IF NOT EXISTS persons ( | ||||
|   id INTEGER PRIMARY KEY, | ||||
|   name TEXT NOT NULL, | ||||
|   email_address TEXT NOT NULL, | ||||
|   github_username TEXT NOT NULL UNIQUE, | ||||
|   person_type_id INTEGER NOT NULL, | ||||
|   team_id INTEGER NULL, | ||||
|   FOREIGN KEY (person_type_id) | ||||
|     REFERENCES person_types(id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE, | ||||
|   FOREIGN KEY (team_id) | ||||
|     REFERENCES teams(id) | ||||
|       ON DELETE CASCADE | ||||
|       ON UPDATE CASCADE | ||||
| ); | ||||
| 
 | ||||
| -- Team tables for all types of teams. | ||||
| CREATE TABLE IF NOT EXISTS team_types ( | ||||
|   id INTEGER PRIMARY KEY, | ||||
|   name TEXT NOT NULL UNIQUE | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE IF NOT EXISTS teams ( | ||||
|   id INTEGER PRIMARY KEY AUTOINCREMENT, | ||||
|   team_type_id INTEGER NOT NULL, | ||||
|   FOREIGN KEY (team_type_id) | ||||
|     REFERENCES team_types(id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE IF NOT EXISTS team_members ( | ||||
|   id INTEGER PRIMARY KEY AUTOINCREMENT, | ||||
|   team_id INTEGER NOT NULL, | ||||
|   person_id INTEGER NOT NULL, | ||||
|   FOREIGN KEY (team_id) | ||||
|     REFERENCES teams(id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE, | ||||
|   FOREIGN KEY (person_id) | ||||
|     REFERENCES persons(id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE, | ||||
|   UNIQUE (team_id, person_id) | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE IF NOT EXISTS teaching_assistant_teams ( | ||||
|   team_id INTEGER PRIMARY KEY, | ||||
|   name TEXT NOT NULL UNIQUE, | ||||
|   FOREIGN KEY (team_id) | ||||
|     REFERENCES teams(id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE IF NOT EXISTS student_teams ( | ||||
|   team_id INTEGER PRIMARY KEY, | ||||
|   repository_name TEXT, | ||||
|   teaching_assistant_team_id INTEGER, | ||||
|   FOREIGN KEY (team_id) | ||||
|     REFERENCES teams(id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE, | ||||
|   FOREIGN KEY (teaching_assistant_team_id) | ||||
|     REFERENCES  teaching_assistant_teams(team_id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE | ||||
| ); | ||||
| 
 | ||||
| -- Student-specific tables. | ||||
| CREATE TABLE IF NOT EXISTS students ( | ||||
|   person_id INTEGER PRIMARY KEY, | ||||
|   chose_partner INTEGER NOT NULL, | ||||
|   FOREIGN KEY (person_id) | ||||
|     REFERENCES persons(id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE IF NOT EXISTS student_preferred_partners ( | ||||
|   student_id INTEGER PRIMARY KEY, | ||||
|   partner_id INTEGER NOT NULL, | ||||
|   FOREIGN KEY (student_id) | ||||
|     REFERENCES students(person_id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE, | ||||
|   UNIQUE (student_id, partner_id) | ||||
| ); | ||||
| 
 | ||||
| -- TeachingAssistant-specific tables. | ||||
| CREATE TABLE IF NOT EXISTS teaching_assistants ( | ||||
|   person_id INTEGER PRIMARY KEY, | ||||
|   FOREIGN KEY (person_id) | ||||
|     REFERENCES persons(id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE | ||||
| ); | ||||
| 
 | ||||
| -- Error queue storage. | ||||
| CREATE TABLE IF NOT EXISTS error_types ( | ||||
|   id INTEGER PRIMARY KEY, | ||||
|   name TEXT NOT NULL UNIQUE | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE IF NOT EXISTS errors ( | ||||
|   id INTEGER PRIMARY KEY, | ||||
|   timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||||
|   error_type_id INTEGER NOT NULL, | ||||
|   message TEXT NOT NULL, | ||||
|   FOREIGN KEY (error_type_id) | ||||
|     REFERENCES error_types(id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE IF NOT EXISTS team_errors ( | ||||
|   error_id INTEGER PRIMARY KEY, | ||||
|   team_id INTEGER NOT NULL, | ||||
|   FOREIGN KEY (error_id) | ||||
|     REFERENCES errors(id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE, | ||||
|   FOREIGN KEY (team_id) | ||||
|     REFERENCES teams(id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE IF NOT EXISTS person_errors ( | ||||
|   error_id INTEGER PRIMARY KEY, | ||||
|   person_id INTEGER NOT NULL, | ||||
|   FOREIGN KEY (error_id) | ||||
|     REFERENCES errors(id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE, | ||||
|   FOREIGN KEY (person_id) | ||||
|     REFERENCES persons(id) | ||||
|     ON DELETE CASCADE | ||||
|     ON UPDATE CASCADE | ||||
| ); | ||||
		Loading…
	
		Reference in New Issue