cleanup: remove Python Flask application files
- Remove Flask server.py and Python models - Delete Alembic migrations and configuration - Remove Python requirements.txt and virtual environment scripts - Clean up old Python service layer and route blueprints
This commit is contained in:
		
							parent
							
								
									5e0ac13488
								
							
						
					
					
						commit
						3028e8f067
					
				
					 13 changed files with 0 additions and 584 deletions
				
			
		|  | @ -1 +0,0 @@ | ||||||
| Single-database configuration for Flask. |  | ||||||
|  | @ -1,50 +0,0 @@ | ||||||
| # A generic, single database configuration. |  | ||||||
| 
 |  | ||||||
| [alembic] |  | ||||||
| # template used to generate migration files |  | ||||||
| # file_template = %%(rev)s_%%(slug)s |  | ||||||
| 
 |  | ||||||
| # set to 'true' to run the environment during |  | ||||||
| # the 'revision' command, regardless of autogenerate |  | ||||||
| # revision_environment = false |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # Logging configuration |  | ||||||
| [loggers] |  | ||||||
| keys = root,sqlalchemy,alembic,flask_migrate |  | ||||||
| 
 |  | ||||||
| [handlers] |  | ||||||
| keys = console |  | ||||||
| 
 |  | ||||||
| [formatters] |  | ||||||
| keys = generic |  | ||||||
| 
 |  | ||||||
| [logger_root] |  | ||||||
| level = WARN |  | ||||||
| handlers = console |  | ||||||
| qualname = |  | ||||||
| 
 |  | ||||||
| [logger_sqlalchemy] |  | ||||||
| level = WARN |  | ||||||
| handlers = |  | ||||||
| qualname = sqlalchemy.engine |  | ||||||
| 
 |  | ||||||
| [logger_alembic] |  | ||||||
| level = INFO |  | ||||||
| handlers = |  | ||||||
| qualname = alembic |  | ||||||
| 
 |  | ||||||
| [logger_flask_migrate] |  | ||||||
| level = INFO |  | ||||||
| handlers = |  | ||||||
| qualname = flask_migrate |  | ||||||
| 
 |  | ||||||
| [handler_console] |  | ||||||
| class = StreamHandler |  | ||||||
| args = (sys.stderr,) |  | ||||||
| level = NOTSET |  | ||||||
| formatter = generic |  | ||||||
| 
 |  | ||||||
| [formatter_generic] |  | ||||||
| format = %(levelname)-5.5s [%(name)s] %(message)s |  | ||||||
| datefmt = %H:%M:%S |  | ||||||
|  | @ -1,113 +0,0 @@ | ||||||
| import logging |  | ||||||
| from logging.config import fileConfig |  | ||||||
| 
 |  | ||||||
| from flask import current_app |  | ||||||
| 
 |  | ||||||
| from alembic import context |  | ||||||
| 
 |  | ||||||
| # this is the Alembic Config object, which provides |  | ||||||
| # access to the values within the .ini file in use. |  | ||||||
| config = context.config |  | ||||||
| 
 |  | ||||||
| # Interpret the config file for Python logging. |  | ||||||
| # This line sets up loggers basically. |  | ||||||
| fileConfig(config.config_file_name) |  | ||||||
| logger = logging.getLogger('alembic.env') |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def get_engine(): |  | ||||||
|     try: |  | ||||||
|         # this works with Flask-SQLAlchemy<3 and Alchemical |  | ||||||
|         return current_app.extensions['migrate'].db.get_engine() |  | ||||||
|     except (TypeError, AttributeError): |  | ||||||
|         # this works with Flask-SQLAlchemy>=3 |  | ||||||
|         return current_app.extensions['migrate'].db.engine |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def get_engine_url(): |  | ||||||
|     try: |  | ||||||
|         return get_engine().url.render_as_string(hide_password=False).replace( |  | ||||||
|             '%', '%%') |  | ||||||
|     except AttributeError: |  | ||||||
|         return str(get_engine().url).replace('%', '%%') |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # add your model's MetaData object here |  | ||||||
| # for 'autogenerate' support |  | ||||||
| # from myapp import mymodel |  | ||||||
| # target_metadata = mymodel.Base.metadata |  | ||||||
| config.set_main_option('sqlalchemy.url', get_engine_url()) |  | ||||||
| target_db = current_app.extensions['migrate'].db |  | ||||||
| 
 |  | ||||||
| # other values from the config, defined by the needs of env.py, |  | ||||||
| # can be acquired: |  | ||||||
| # my_important_option = config.get_main_option("my_important_option") |  | ||||||
| # ... etc. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def get_metadata(): |  | ||||||
|     if hasattr(target_db, 'metadatas'): |  | ||||||
|         return target_db.metadatas[None] |  | ||||||
|     return target_db.metadata |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def run_migrations_offline(): |  | ||||||
|     """Run migrations in 'offline' mode. |  | ||||||
| 
 |  | ||||||
|     This configures the context with just a URL |  | ||||||
|     and not an Engine, though an Engine is acceptable |  | ||||||
|     here as well.  By skipping the Engine creation |  | ||||||
|     we don't even need a DBAPI to be available. |  | ||||||
| 
 |  | ||||||
|     Calls to context.execute() here emit the given string to the |  | ||||||
|     script output. |  | ||||||
| 
 |  | ||||||
|     """ |  | ||||||
|     url = config.get_main_option("sqlalchemy.url") |  | ||||||
|     context.configure( |  | ||||||
|         url=url, target_metadata=get_metadata(), literal_binds=True |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     with context.begin_transaction(): |  | ||||||
|         context.run_migrations() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def run_migrations_online(): |  | ||||||
|     """Run migrations in 'online' mode. |  | ||||||
| 
 |  | ||||||
|     In this scenario we need to create an Engine |  | ||||||
|     and associate a connection with the context. |  | ||||||
| 
 |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|     # this callback is used to prevent an auto-migration from being generated |  | ||||||
|     # when there are no changes to the schema |  | ||||||
|     # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html |  | ||||||
|     def process_revision_directives(context, revision, directives): |  | ||||||
|         if getattr(config.cmd_opts, 'autogenerate', False): |  | ||||||
|             script = directives[0] |  | ||||||
|             if script.upgrade_ops.is_empty(): |  | ||||||
|                 directives[:] = [] |  | ||||||
|                 logger.info('No changes in schema detected.') |  | ||||||
| 
 |  | ||||||
|     conf_args = current_app.extensions['migrate'].configure_args |  | ||||||
|     if conf_args.get("process_revision_directives") is None: |  | ||||||
|         conf_args["process_revision_directives"] = process_revision_directives |  | ||||||
| 
 |  | ||||||
|     connectable = get_engine() |  | ||||||
| 
 |  | ||||||
|     with connectable.connect() as connection: |  | ||||||
|         context.configure( |  | ||||||
|             connection=connection, |  | ||||||
|             target_metadata=get_metadata(), |  | ||||||
|             **conf_args |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         with context.begin_transaction(): |  | ||||||
|             context.run_migrations() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| if context.is_offline_mode(): |  | ||||||
|     run_migrations_offline() |  | ||||||
| else: |  | ||||||
|     run_migrations_online() |  | ||||||
|  | @ -1,24 +0,0 @@ | ||||||
| """${message} |  | ||||||
| 
 |  | ||||||
| Revision ID: ${up_revision} |  | ||||||
| Revises: ${down_revision | comma,n} |  | ||||||
| Create Date: ${create_date} |  | ||||||
| 
 |  | ||||||
| """ |  | ||||||
| from alembic import op |  | ||||||
| import sqlalchemy as sa |  | ||||||
| ${imports if imports else ""} |  | ||||||
| 
 |  | ||||||
| # revision identifiers, used by Alembic. |  | ||||||
| revision = ${repr(up_revision)} |  | ||||||
| down_revision = ${repr(down_revision)} |  | ||||||
| branch_labels = ${repr(branch_labels)} |  | ||||||
| depends_on = ${repr(depends_on)} |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def upgrade(): |  | ||||||
|     ${upgrades if upgrades else "pass"} |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def downgrade(): |  | ||||||
|     ${downgrades if downgrades else "pass"} |  | ||||||
|  | @ -1,99 +0,0 @@ | ||||||
| """Initial migration |  | ||||||
| 
 |  | ||||||
| Revision ID: 0e07095d2961 |  | ||||||
| Revises:  |  | ||||||
| Create Date: 2025-08-29 01:28:57.822103 |  | ||||||
| 
 |  | ||||||
| """ |  | ||||||
| from alembic import op |  | ||||||
| import sqlalchemy as sa |  | ||||||
| from sqlalchemy.dialects import postgresql |  | ||||||
| 
 |  | ||||||
| # revision identifiers, used by Alembic. |  | ||||||
| revision = '0e07095d2961' |  | ||||||
| down_revision = None |  | ||||||
| branch_labels = None |  | ||||||
| depends_on = None |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def upgrade(): |  | ||||||
|     # ### commands auto generated by Alembic - please adjust! ### |  | ||||||
|     op.drop_table('admins') |  | ||||||
|     with op.batch_alter_table('subscribers', schema=None) as batch_op: |  | ||||||
|         batch_op.drop_index(batch_op.f('idx_subscribers_created_at')) |  | ||||||
|         batch_op.drop_index(batch_op.f('idx_subscribers_email')) |  | ||||||
|         batch_op.drop_index(batch_op.f('idx_subscribers_status')) |  | ||||||
| 
 |  | ||||||
|     op.drop_table('subscribers') |  | ||||||
|     op.drop_table('admin_users') |  | ||||||
|     op.drop_table('email_deliveries') |  | ||||||
|     with op.batch_alter_table('newsletters', schema=None) as batch_op: |  | ||||||
|         batch_op.drop_index(batch_op.f('idx_newsletters_sent_at')) |  | ||||||
| 
 |  | ||||||
|     op.drop_table('newsletters') |  | ||||||
|     # ### end Alembic commands ### |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def downgrade(): |  | ||||||
|     # ### commands auto generated by Alembic - please adjust! ### |  | ||||||
|     op.create_table('newsletters', |  | ||||||
|     sa.Column('id', sa.INTEGER(), server_default=sa.text("nextval('newsletters_id_seq'::regclass)"), autoincrement=True, nullable=False), |  | ||||||
|     sa.Column('subject', sa.TEXT(), autoincrement=False, nullable=False), |  | ||||||
|     sa.Column('body', sa.TEXT(), autoincrement=False, nullable=False), |  | ||||||
|     sa.Column('sent_at', postgresql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=True), |  | ||||||
|     sa.Column('sent_by', sa.TEXT(), autoincrement=False, nullable=True), |  | ||||||
|     sa.Column('recipient_count', sa.INTEGER(), server_default=sa.text('0'), autoincrement=False, nullable=True), |  | ||||||
|     sa.Column('success_count', sa.INTEGER(), server_default=sa.text('0'), autoincrement=False, nullable=True), |  | ||||||
|     sa.Column('failure_count', sa.INTEGER(), server_default=sa.text('0'), autoincrement=False, nullable=True), |  | ||||||
|     sa.PrimaryKeyConstraint('id', name='newsletters_pkey'), |  | ||||||
|     postgresql_ignore_search_path=False |  | ||||||
|     ) |  | ||||||
|     with op.batch_alter_table('newsletters', schema=None) as batch_op: |  | ||||||
|         batch_op.create_index(batch_op.f('idx_newsletters_sent_at'), [sa.literal_column('sent_at DESC')], unique=False) |  | ||||||
| 
 |  | ||||||
|     op.create_table('email_deliveries', |  | ||||||
|     sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), |  | ||||||
|     sa.Column('newsletter_id', sa.INTEGER(), autoincrement=False, nullable=True), |  | ||||||
|     sa.Column('email', sa.TEXT(), autoincrement=False, nullable=False), |  | ||||||
|     sa.Column('status', sa.TEXT(), autoincrement=False, nullable=True), |  | ||||||
|     sa.Column('sent_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=True), |  | ||||||
|     sa.Column('error_message', sa.TEXT(), autoincrement=False, nullable=True), |  | ||||||
|     sa.CheckConstraint("status = ANY (ARRAY['sent'::text, 'failed'::text, 'bounced'::text])", name=op.f('email_deliveries_status_check')), |  | ||||||
|     sa.ForeignKeyConstraint(['newsletter_id'], ['newsletters.id'], name=op.f('email_deliveries_newsletter_id_fkey')), |  | ||||||
|     sa.PrimaryKeyConstraint('id', name=op.f('email_deliveries_pkey')) |  | ||||||
|     ) |  | ||||||
|     op.create_table('admin_users', |  | ||||||
|     sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), |  | ||||||
|     sa.Column('username', sa.TEXT(), autoincrement=False, nullable=False), |  | ||||||
|     sa.Column('password', sa.TEXT(), autoincrement=False, nullable=False), |  | ||||||
|     sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=True), |  | ||||||
|     sa.Column('last_login', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True), |  | ||||||
|     sa.Column('is_active', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=True), |  | ||||||
|     sa.PrimaryKeyConstraint('id', name=op.f('admin_users_pkey')), |  | ||||||
|     sa.UniqueConstraint('username', name=op.f('admin_users_username_key'), postgresql_include=[], postgresql_nulls_not_distinct=False) |  | ||||||
|     ) |  | ||||||
|     op.create_table('subscribers', |  | ||||||
|     sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), |  | ||||||
|     sa.Column('email', sa.TEXT(), autoincrement=False, nullable=False), |  | ||||||
|     sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=True), |  | ||||||
|     sa.Column('subscribed_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=True), |  | ||||||
|     sa.Column('status', sa.TEXT(), server_default=sa.text("'active'::text"), autoincrement=False, nullable=True), |  | ||||||
|     sa.Column('source', sa.TEXT(), server_default=sa.text("'manual'::text"), autoincrement=False, nullable=True), |  | ||||||
|     sa.CheckConstraint("status = ANY (ARRAY['active'::text, 'unsubscribed'::text])", name=op.f('subscribers_status_check')), |  | ||||||
|     sa.PrimaryKeyConstraint('id', name=op.f('subscribers_pkey')), |  | ||||||
|     sa.UniqueConstraint('email', name=op.f('subscribers_email_key'), postgresql_include=[], postgresql_nulls_not_distinct=False) |  | ||||||
|     ) |  | ||||||
|     with op.batch_alter_table('subscribers', schema=None) as batch_op: |  | ||||||
|         batch_op.create_index(batch_op.f('idx_subscribers_status'), ['status'], unique=False) |  | ||||||
|         batch_op.create_index(batch_op.f('idx_subscribers_email'), ['email'], unique=False) |  | ||||||
|         batch_op.create_index(batch_op.f('idx_subscribers_created_at'), [sa.literal_column('created_at DESC')], unique=False) |  | ||||||
| 
 |  | ||||||
|     op.create_table('admins', |  | ||||||
|     sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), |  | ||||||
|     sa.Column('username', sa.VARCHAR(length=100), autoincrement=False, nullable=False), |  | ||||||
|     sa.Column('password_hash', sa.VARCHAR(length=255), autoincrement=False, nullable=False), |  | ||||||
|     sa.Column('created_at', postgresql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=True), |  | ||||||
|     sa.PrimaryKeyConstraint('id', name=op.f('admins_pkey')), |  | ||||||
|     sa.UniqueConstraint('username', name=op.f('admins_username_key'), postgresql_include=[], postgresql_nulls_not_distinct=False) |  | ||||||
|     ) |  | ||||||
|     # ### end Alembic commands ### |  | ||||||
|  | @ -1,40 +0,0 @@ | ||||||
| package models |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"golang.org/x/crypto/bcrypt" |  | ||||||
| 	"gorm.io/gorm" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type User struct { |  | ||||||
| 	ID       uint   `gorm:"primaryKey" json:"id"` |  | ||||||
| 	Username string `gorm:"unique;not null;size:80" json:"username"` |  | ||||||
| 	Email    string `gorm:"unique;not null;size:255" json:"email"` // Add this line |  | ||||||
| 	Password string `gorm:"not null;size:255" json:"-"` |  | ||||||
| 
 |  | ||||||
| 	Profile *UserProfile `gorm:"constraint:OnDelete:CASCADE;" json:"profile,omitempty"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (u *User) SetPassword(password string) error { |  | ||||||
| 	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	u.Password = string(hashedPassword) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (u *User) CheckPassword(password string) bool { |  | ||||||
| 	err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) |  | ||||||
| 	return err == nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (u *User) AfterCreate(tx *gorm.DB) error { |  | ||||||
| 	profile := UserProfile{ |  | ||||||
| 		UserID:         u.ID, |  | ||||||
| 		FirstName:      "", |  | ||||||
| 		LastName:       "", |  | ||||||
| 		Bio:            "", |  | ||||||
| 		ProfilePicture: "", |  | ||||||
| 	} |  | ||||||
| 	return tx.Create(&profile).Error |  | ||||||
| } |  | ||||||
|  | @ -1,12 +0,0 @@ | ||||||
| package models |  | ||||||
| 
 |  | ||||||
| type UserProfile struct { |  | ||||||
| 	ID             uint   `gorm:"primaryKey" json:"id"` |  | ||||||
| 	UserID         uint   `gorm:"not null" json:"user_id"` |  | ||||||
| 	FirstName      string `gorm:"size:80;not null" json:"first_name"` |  | ||||||
| 	LastName       string `gorm:"size:80;not null" json:"last_name"` |  | ||||||
| 	Bio            string `gorm:"type:text" json:"bio"` |  | ||||||
| 	ProfilePicture string `gorm:"size:255" json:"profile_picture"` |  | ||||||
| 
 |  | ||||||
| 	User *User `gorm:"foreignKey:UserID" json:"user,omitempty"` |  | ||||||
| } |  | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| Flask |  | ||||||
| flask_bcrypt |  | ||||||
| flask_cors |  | ||||||
| flask_sqlalchemy |  | ||||||
| python-dotenv |  | ||||||
| werkzeug |  | ||||||
| psycopg2-binary |  | ||||||
| Flask-Migrate |  | ||||||
|  | @ -1,89 +0,0 @@ | ||||||
| package routes |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
| 
 |  | ||||||
| 	"github.com/gin-contrib/sessions" |  | ||||||
| 	"github.com/gin-gonic/gin" |  | ||||||
| 	"gorm.io/gorm" |  | ||||||
| 
 |  | ||||||
| 	"github.com/rideaware/rideaware-api/services" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func RegisterAuthRoutes(r *gin.Engine, db *gorm.DB) { |  | ||||||
| 	userService := services.NewUserService(db) |  | ||||||
| 
 |  | ||||||
| 	auth := r.Group("/auth") |  | ||||||
| 	{ |  | ||||||
| 		auth.POST("/signup", signup(userService)) |  | ||||||
| 		auth.POST("/login", login(userService)) |  | ||||||
| 		auth.POST("/logout", logout()) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func signup(userService *services.UserService) gin.HandlerFunc { |  | ||||||
| 	return func(c *gin.Context) { |  | ||||||
| 		var req struct { |  | ||||||
| 			Username string `json:"username" binding:"required"` |  | ||||||
| 			Email    string `json:"email" binding:"required"` |  | ||||||
| 			Password string `json:"password" binding:"required"` |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if err := c.ShouldBindJSON(&req); err != nil { |  | ||||||
| 			c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()}) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		user, err := userService.CreateUser(req.Username, req.Email, req.Password) |  | ||||||
| 		if err != nil { |  | ||||||
| 			c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()}) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		c.JSON(http.StatusCreated, gin.H{ |  | ||||||
| 			"message":  "User created successfully", |  | ||||||
| 			"username": user.Username, |  | ||||||
| 			"email":    user.Email, |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func login(userService *services.UserService) gin.HandlerFunc { |  | ||||||
| 	return func(c *gin.Context) { |  | ||||||
| 		var req struct { |  | ||||||
| 			Username string `json:"username" binding:"required"` |  | ||||||
| 			Password string `json:"password" binding:"required"` |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if err := c.ShouldBindJSON(&req); err != nil { |  | ||||||
| 			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		user, err := userService.VerifyUser(req.Username, req.Password) |  | ||||||
| 		if err != nil { |  | ||||||
| 			c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Set session |  | ||||||
| 		session := sessions.Default(c) |  | ||||||
| 		session.Set("user_id", user.ID) |  | ||||||
| 		session.Save() |  | ||||||
| 
 |  | ||||||
| 		c.JSON(http.StatusOK, gin.H{ |  | ||||||
| 			"message": "Login successful", |  | ||||||
| 			"user_id": user.ID, |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func logout() gin.HandlerFunc { |  | ||||||
| 	return func(c *gin.Context) { |  | ||||||
| 		session := sessions.Default(c) |  | ||||||
| 		session.Clear() |  | ||||||
| 		session.Save() |  | ||||||
| 
 |  | ||||||
| 		c.JSON(http.StatusOK, gin.H{"message": "Logout successful"}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| #!/bin/bash |  | ||||||
| set -e |  | ||||||
| 
 |  | ||||||
| echo "Running database migrations..." |  | ||||||
| flask db upgrade |  | ||||||
| 
 |  | ||||||
| echo "Starting application..." |  | ||||||
| exec "$@" |  | ||||||
							
								
								
									
										33
									
								
								server.py
									
										
									
									
									
								
							
							
						
						
									
										33
									
								
								server.py
									
										
									
									
									
								
							|  | @ -1,33 +0,0 @@ | ||||||
| import os |  | ||||||
| from flask import Flask |  | ||||||
| from flask_cors import CORS |  | ||||||
| from dotenv import load_dotenv |  | ||||||
| from flask_migrate import Migrate |  | ||||||
| from flask.cli import FlaskGroup |  | ||||||
| 
 |  | ||||||
| from models import db, init_db |  | ||||||
| from routes.user_auth import auth |  | ||||||
| 
 |  | ||||||
| load_dotenv() |  | ||||||
| 
 |  | ||||||
| app = Flask(__name__) |  | ||||||
| app.config["SECRET_KEY"] = os.getenv("SECRET_KEY") |  | ||||||
| app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("DATABASE") |  | ||||||
| app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False |  | ||||||
| 
 |  | ||||||
| CORS(app) |  | ||||||
| 
 |  | ||||||
| init_db(app) |  | ||||||
| migrate = Migrate(app, db) |  | ||||||
| app.register_blueprint(auth.auth_bp) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @app.route("/health") |  | ||||||
| def health_check(): |  | ||||||
|     """Health check endpoint.""" |  | ||||||
|     return "OK", 200 |  | ||||||
| 
 |  | ||||||
| cli = FlaskGroup(app) |  | ||||||
| 
 |  | ||||||
| if __name__ == "__main__": |  | ||||||
|     cli() |  | ||||||
|  | @ -1,34 +0,0 @@ | ||||||
| package services |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/smtp" |  | ||||||
| 	"os" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type EmailService struct { |  | ||||||
| 	smtpHost     string |  | ||||||
| 	smtpPort     string |  | ||||||
| 	smtpUser     string |  | ||||||
| 	smtpPassword string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func NewEmailService() *EmailService { |  | ||||||
| 	return &EmailService{ |  | ||||||
| 		smtpHost:     os.Getenv("SMTP_SERVER"), |  | ||||||
| 		smtpPort:     os.Getenv("SMTP_PORT"), |  | ||||||
| 		smtpUser:     os.Getenv("SMTP_USER"), |  | ||||||
| 		smtpPassword: os.Getenv("SMTP_PASSWORD"), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (e *EmailService) SendEmail(to, subject, body string) error { |  | ||||||
| 	from := e.smtpUser |  | ||||||
| 
 |  | ||||||
| 	msg := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s", from, to, subject, body) |  | ||||||
| 
 |  | ||||||
| 	auth := smtp.PlainAuth("", e.smtpUser, e.smtpPassword, e.smtpHost) |  | ||||||
| 	addr := fmt.Sprintf("%s:%s", e.smtpHost, e.smtpPort) |  | ||||||
| 
 |  | ||||||
| 	return smtp.SendMail(addr, auth, from, []string{to}, []byte(msg)) |  | ||||||
| } |  | ||||||
|  | @ -1,73 +0,0 @@ | ||||||
| package services |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"log" |  | ||||||
| 	"strings" |  | ||||||
| 
 |  | ||||||
| 	"github.com/rideaware/rideaware-api/models" |  | ||||||
| 	"gorm.io/gorm" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type UserService struct { |  | ||||||
| 	db *gorm.DB |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func NewUserService(db *gorm.DB) *UserService { |  | ||||||
| 	return &UserService{db: db} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (s *UserService) CreateUser(username, email, password string) (*models.User, error) { |  | ||||||
| 	if username == "" || email == "" || password == "" { |  | ||||||
| 		return nil, errors.New("username, email, and password are required") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if len(username) < 3 || len(password) < 8 { |  | ||||||
| 		return nil, errors.New("username must be at least 3 characters and password must be at least 8 characters") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Basic email validation |  | ||||||
| 	if !strings.Contains(email, "@") { |  | ||||||
| 		return nil, errors.New("invalid email format") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Check if user exists (by username or email) |  | ||||||
| 	var existingUser models.User |  | ||||||
| 	if err := s.db.Where("username = ? OR email = ?", username, email).First(&existingUser).Error; err == nil { |  | ||||||
| 		return nil, errors.New("user with this username or email already exists") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Create new user |  | ||||||
| 	user := models.User{ |  | ||||||
| 		Username: username, |  | ||||||
| 		Email:    email, |  | ||||||
| 	} |  | ||||||
| 	if err := user.SetPassword(password); err != nil { |  | ||||||
| 		log.Printf("Error hashing password: %v", err) |  | ||||||
| 		return nil, errors.New("could not create user") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := s.db.Create(&user).Error; err != nil { |  | ||||||
| 		log.Printf("Error creating user: %v", err) |  | ||||||
| 		return nil, errors.New("could not create user") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return &user, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (s *UserService) VerifyUser(username, password string) (*models.User, error) { |  | ||||||
| 	var user models.User |  | ||||||
| 	// Allow login with either username or email |  | ||||||
| 	if err := s.db.Where("username = ? OR email = ?", username, username).First(&user).Error; err != nil { |  | ||||||
| 		log.Printf("User not found: %s", username) |  | ||||||
| 		return nil, errors.New("invalid username or password") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !user.CheckPassword(password) { |  | ||||||
| 		log.Printf("Invalid password for user: %s", username) |  | ||||||
| 		return nil, errors.New("invalid username or password") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	log.Printf("User verified: %s", username) |  | ||||||
| 	return &user, nil |  | ||||||
| } |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Cipher Vance
						Cipher Vance